Пример #1
0
class AccountReconcileVATLine(Model):
    _name = "account.reconcile.vat.line"
    _name_field = "rec_id"
    _fields = {
        "rec_id":
        fields.Many2One("account.reconcile.vat",
                        "Reconcile ID",
                        required=True,
                        on_delete="cascade"),
        "tax_date":
        fields.Date("Tax Date"),
        "related_id":
        fields.Reference(
            [["account.invoice", "Invoice"], ["account.payment", "Payment"],
             ["account.move", "Move"]],
            "Related To",
            required=True),
        "tax_no":
        fields.Char("Tax No."),
        "contact_id":
        fields.Many2One("contact", "Contact", required=True),
        "tax_base":
        fields.Decimal("Tax Base", required=True),
        "tax_amount":
        fields.Decimal("Tax Amount", required=True),
        "move_line_id":
        fields.Many2One("account.move.line", "Move Line ID",
                        required=True),  #Use To Reconcile
    }

    _order = "tax_date desc"
Пример #2
0
class RequiredDoc(Model):
    _name = "required.doc"
    _string = "Required Document"
    _fields = {
        "name": fields.Text("Document Name", required=True),
        "related_id": fields.Reference([], "Related To"),
    }
Пример #3
0
class BarcodeIssueLine(Model):
    _name = "barcode.issue.line"
    _transient = True
    _fields = {
        "wizard_id":
        fields.Many2One("barcode.issue",
                        "Wizard",
                        required=True,
                        on_delete="cascade"),
        "product_id":
        fields.Many2One("product", "Product", required=True),
        "qty":
        fields.Decimal("Qty", required=True),
        "uom_id":
        fields.Many2One("uom", "UoM", required=True),
        "qty2":
        fields.Decimal("Secondary Qty"),
        "lot_id":
        fields.Many2One("stock.lot", "Lot / Serial Number"),
        "container_from_id":
        fields.Many2One("stock.container", "From Container"),
        "container_to_id":
        fields.Many2One("stock.container", "To Container"),
        "location_from_id":
        fields.Many2One("stock.location", "From Location"),
        "location_to_id":
        fields.Many2One("stock.location", "To Location"),
        "related_id":
        fields.Reference([["sale.order", "Sales Order"],
                          ["purchase.order", "Purchase Order"]], "Related To"),
        "qty2":
        fields.Decimal("Qty2"),
        "notes":
        fields.Text("Notes"),
    }
Пример #4
0
class Barcode(Model):
    _inherit = "stock.barcode"
    _transient = True

    _fields = {
        "related_id":
        fields.Reference([["sale.order", "Sales Order"],
                          ["purchase.order", "Purchase Order"],
                          ["production.order", "Production Order"],
                          ["stock.picking", "Picking"]], "Related To"),
    }

    def onchange_related(self, context={}):
        data = context["data"]
        type = data["type"]
        val = data["related_id"][0]
        relation, rel_id = val.split(",")
        rel_id = int(rel_id)
        if relation == "production.order":
            rel = get_model("production.order").browse(rel_id)
            if type == "out":
                data["location_to_id"] = rel.production_location_id.id
            elif type == "in":
                data["location_from_id"] = rel.production_location_id.id
        return data
Пример #5
0
class Transform(Model):
    _inherit = "stock.transform"
    _fields = {
        "related_id":
        fields.Reference(
            [["sale.order", "Sales Order"], [
                "purchase.order", "Purchase Order"
            ], ["production.order", "Production Order"],
             ["job", "Service Order"], ["product.claim", "Claim Bill"],
             ["product.borrow", "Borrow Request"],
             ["stock.picking", "Picking"]], "Related To"),
    }
Пример #6
0
class Block(Model):
    _name = "cms.block"
    _string = "Block"
    _fields = {
        "name":
        fields.Char("Name", required=True, search=True),
        "related_id":
        fields.Reference([["cms.page", "Page"], ["cms.blog.post", "Post"]],
                         "Related To"),
        "html":
        fields.Text("HTML", search=True, translate=True),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
    }
    _order = "name"

    def get_block(self, name, page_id=None, post_id=None, context={}):
        # print("get_block",name,page_id,post_id)
        dbname = get_active_db()
        lang = get_active_locale()
        key = (dbname, name, page_id, post_id, lang)
        if key in _block_cache:
            #print("...cache hit")
            return _block_cache[key]
        #print("...cache miss")
        cond = [["name", "=", name]]
        if page_id:
            cond.append(["related_id", "=", "cms.page,%d" % page_id])
        if post_id:
            cond.append(["related_id", "=", "cms.blog.post,%d" % post_id])
        res = self.search(cond)
        if res:
            block = self.read(res, ["html"])[0]
        else:
            block = None
        _block_cache[key] = block
        return block

    def create(self, *a, **kw):
        res = super().create(*a, **kw)
        ipc.send_signal("clear_block_cache")

    def write(self, *a, **kw):
        res = super().write(*a, **kw)
        ipc.send_signal("clear_block_cache")

    def delete(self, *a, **kw):
        res = super().delete(*a, **kw)
        ipc.send_signal("clear_block_cache")
Пример #7
0
class CustomCurrencyRate(Model):
    _name = "custom.currency.rate"
    _fields = {
        "related_id":
        fields.Reference(
            [["quotation", "Quotation"], ["sale.order", "Sales Order"]],
            "Related To"),
        "currency_id":
        fields.Many2One("currency",
                        "Currency",
                        required=True,
                        on_delete="cascade"),
        "rate":
        fields.Decimal("Rate", scale=6, required=True),
    }
Пример #8
0
class BarcodeValidateLine(Model):
    _name = "barcode.validate.line"
    _transient = True
    _fields = {
        "wizard_id": fields.Many2One("barcode.validate", "Wizard", required=True, on_delete="cascade"),
        "product_id": fields.Many2One("product", "Product", required=True),
        "qty_planned": fields.Decimal("Planned Qty", required=True, readonly=True),
        "qty_actual": fields.Decimal("Validated Qty"),
        "uom_id": fields.Many2One("uom", "UoM", required=True),
        "lot_id": fields.Many2One("stock.lot", "Lot / Serial Number"),
        "container_to_id": fields.Many2One("stock.container", "Container"),
        "location_from_id": fields.Many2One("stock.location", "From Location"),
        "location_to_id": fields.Many2One("stock.location", "To Location"),
        "related_id": fields.Reference([["sale.order", "Sales Order"], ["purchase.order", "Purchase Order"], ["job", "Service Order"], ["product.claim", "Claim Bill"], ["product.borrow", "Borrow Request"], ["stock.picking", "Picking"]], "Related To"),
    }
Пример #9
0
class ShareRecord(Model):
    _name = "share.record"
    _fields = {
        "related_id":
        fields.Reference([], "Related To"),
        "user_id":
        fields.Many2One("base.user", "User", required=True),
        "access":
        fields.Selection([["r", "Read Only"], ["rw", "Read/Write"]],
                         "Access",
                         required=True),
    }
    _defaults = {
        "access": "r",
    }
class TrackEntry(Model):
    _name = "account.track.entry"
    _string = "Tracking Entries"
    _fields = {
        "track_id": fields.Many2One("account.track.categ", "Tracking Category",required=True,on_delete="cascade",search=True),
        "date": fields.Date("Date",required=True,search=True),
        "amount": fields.Decimal("Amount",required=True),
        "product_id": fields.Many2One("product","Product",search=True),
        "description": fields.Text("Description"),
        "qty": fields.Decimal("Qty"),
        "uom_id": fields.Many2One("uom","UoM"),
        "unit_price": fields.Decimal("Unit Price"),
        "related_id": fields.Reference([["account.invoice","Invoice"],["stock.picking","Stock Picking"],["work.time","Work Time"],["hr.expense","Expense Claim"]],"Related To"),
        "move_id": fields.Many2One("account.move","Journal Entry",search=True),
    }
    _order = "date desc,id desc"
    _defaults={
        "date": lambda *a: time.strftime("%Y-%m-%d"),
    }

    def onchange_product(self,context={}):
        data=context.get("data",{})
        print("#"*80)
        print("ID",data.get("id"))
        prod_id=data["product_id"]
        if not prod_id:
            return
        prod=get_model("product").browse(prod_id)
        price=prod.cost_price if prod.cost_method == "standard" else prod.landed_cost
        if not price:
            raise Exception("Missing Cost Price or Landed Cost")
        track_id=data["track_id"]
        track=get_model("account.track.categ").browse(track_id)
        if track.currency_id:
            settings=get_model("settings").browse(1)
            price=get_model("currency").convert(price,settings.currency_id.id,track.currency_id.id)
        data["unit_price"]=-price
        data["qty"]=1
        data["uom_id"]=prod.uom_id.id
        data["amount"]=data["unit_price"]
        return data

    def update_amount(self,context={}):
        data=context.get("data",{})
        unit_price=data.get("unit_price",0)
        qty=data.get("qty",0)
        data["amount"]=unit_price*qty
        return data
Пример #11
0
class StockPicking(Model):
    _inherit= "stock.picking"

    _fields = {

         "related_id": fields.Reference([["sale.order", "Sales Order"],
            ["purchase.order", "Purchase Order"],
            ["production.order", "Production Order"],
            ["project","Project"],
            ["job", "Service Order"],
            ["product.claim", "Claim Bill"],
            ["product.borrow", "Borrow Request"],
            ["stock.picking", "Picking"]], "Related To"),


        }
    def get_update_production_orders(self, ids, context={}):
        prod_ids = []
        for obj in self.browse(ids):
            for line in obj.lines:
                prod_ids.append(line.product_id.id)
        prod_ids = list(set(prod_ids))
        production_ids = []
        for comp in get_model("production.component").\
                search_browse([["product_id", "in", prod_ids]]):
            production_ids.append(comp.order_id.id)
        return list(set(production_ids))


    def set_done(self,ids,context={}):
        user_id=get_active_user()
        for obj in self.browse(ids):
            move_ids=[]
            for line in obj.lines:
                move_ids.append(line.id)
            desc=obj.number
            get_model("stock.move").write(move_ids,vals={"date":obj.date,
                "journal_id":obj.journal_id.id,
                "ref":obj.number},context=context)
            get_model("stock.move").set_done(move_ids,context=context)
            obj.write({"state":"done","done_by_id":user_id},context=context)
            obj.set_currency_rate()
        self.check_order_qtys(ids)
        production_ids=self.get_update_production_orders(ids)
        if production_ids:
            get_model("production.order").update_status(production_ids)
        self.trigger(ids,"done")
Пример #12
0
class Attach(Model):
    _name = "attach"
    _string = "Attachment"
    _order = "date desc"
    _fields = {
        "date": fields.DateTime("Date", required=True, search=True),
        "user_id": fields.Many2One("base.user", "User", search=True),
        "file": fields.File("File", required=True),
        "related_id": fields.Reference([["document", "Document"]], "Related To"),
        "description": fields.Text("Description", search=True),
        "comments": fields.One2Many("message", "related_id", "Comments"),
    }

    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "user_id": lambda self, context: int(context.get("user_id")),
    }
Пример #13
0
class Log(Model):
    _name = "log"
    _string = "Log Entry"
    _fields = {
        "date": fields.DateTime("Date", required=True, search=True),
        "user_id": fields.Many2One("base.user", "User", search=True),
        "ip_addr": fields.Char("IP Address", search=True),
        "country_id": fields.Many2One("country", "Country", readonly=True),
        "message": fields.Text("Message", required=True, search=True),
        "details": fields.Text("Details", search=True),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "related_id": fields.Reference([], "Related To"),
    }
    _defaults = {
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
    }
    _order = "id desc"

    def log(self, msg, details=None, ip_addr=None, related_id=None):
        uid = get_active_user()
        if not ip_addr:
            ip_addr = get_ip_addr()
        try:
            country_code = get_ip_country(ip_addr)
            res = get_model("country").search([["code", "=", country_code]])
            country_id = res[0]
        except Exception as e:
            #print("Failed to get IP country: %s"%e)
            country_id = None
        vals = {
            "user_id": uid,
            "ip_addr": ip_addr,
            "message": msg,
            "details": details,
            "country_id": country_id,
            "related_id": related_id,
        }
        set_active_user(1)
        self.create(vals)
        set_active_user(uid)
Пример #14
0
class Move(Model):
    _inherit = "stock.move"

    _fields = {
        "related_id":
        fields.Reference(
            [["sale.order", "Sales Order"], [
                "purchase.order", "Purchase Order"
            ], ["production.order", "Production Order"],
             ["job", "Service Order"], ["account.invoice", "Invoice"],
             ["pawn.loan", "Loan"]], "Related To"),
    }

    def get_production_orders(self, ids, context={}):
        prod_ids = []
        for obj in self.browse(ids):
            prod_ids.append(obj.product_id.id)
        prod_ids = list(set(prod_ids))
        production_ids = []
        for comp in get_model("production.component").\
                search_browse([["product_id", "in", prod_ids]]):
            production_ids.append(comp.order_id.id)
        return list(set(production_ids))
Пример #15
0
class SaleQuot(Model):
    _name = "sale.quot"
    _string = "Quotation"
    _audit_log = True
    _name_field = "number"
    _key = ["number"]
    _multi_company = True
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "ref":
        fields.Char("Ref", search=True),
        "contact_id":
        fields.Many2One("contact", "Contact", required=True, search=True),
        "date":
        fields.Date("Date", required=True, search=True),
        "exp_date":
        fields.Date("Valid Until"),
        "state":
        fields.Selection([("draft", "Draft"),
                          ("waiting_approval", "Awaiting Approval"),
                          ("approved", "Approved"), ("won", "Won"),
                          ("lost", "Lost"), ("revised", "Revised")],
                         "Status",
                         function="get_state",
                         store=True),
        "lines":
        fields.One2Many("sale.quot.line", "quot_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_words":
        fields.Char("Total Words", function="get_amount_total_words"),
        "qty_total":
        fields.Decimal("Total", function="get_qty_total"),
        "currency_id":
        fields.Many2One("currency", "Currency", required=True),
        "opport_id":
        fields.Many2One("sale.opportunity", "Opportunity", search=True),
        "user_id":
        fields.Many2One("base.user", "Owner", search=True),
        "tax_type":
        fields.Selection([["tax_ex", "Tax Exclusive"],
                          ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]],
                         "Tax Type",
                         required=True),
        "sales":
        fields.One2Many("sale.order", "quot_id", "Sales Orders"),
        "payment_terms":
        fields.Text("Payment Terms"),
        "other_info":
        fields.Text("Other Information"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "activities":
        fields.One2Many("activity", "related_id", "Activities"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "uuid":
        fields.Char("UUID"),
        "price_list_id":
        fields.Many2One("price.list", "Price List"),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "company_id":
        fields.Many2One("company", "Company"),
        "related_id":
        fields.Reference([["issue", "Issue"]], "Related To"),
        "ship_term_id":
        fields.Many2One("ship.term", "Shipping Terms"),
        "sequence_id":
        fields.Many2One("sequence", "Number Sequence"),
        "job_template_id":
        fields.Many2One("job.template", "Service Order Template"),
        "lost_sale_code_id":
        fields.Many2One("reason.code",
                        "Lost Sale Reason Code",
                        condition=[["type", "=", "lost_sale"]]),
        "agg_amount_total":
        fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]),
        "agg_amount_subtotal":
        fields.Decimal("Total Amount w/o Tax",
                       agg_function=["sum", "amount_subtotal"]),
        "year":
        fields.Char("Year", sql_function=["year", "date"]),
        "quarter":
        fields.Char("Quarter", sql_function=["quarter", "date"]),
        "month":
        fields.Char("Month", sql_function=["month", "date"]),
        "week":
        fields.Char("Week", sql_function=["week", "date"]),
        "est_costs":
        fields.One2Many("quot.cost", "quot_id", "Costs"),
        "est_cost_amount":
        fields.Float("Estimated Cost Amount",
                     function="get_est_profit",
                     function_multi=True),
        "est_profit_amount":
        fields.Float("Estimated Profit Amount",
                     function="get_est_profit",
                     function_multi=True),
        "est_margin_percent":
        fields.Float("Estimated Margin %",
                     function="get_est_profit",
                     function_multi=True),
        "currency_rates":
        fields.One2Many("custom.currency.rate", "related_id",
                        "Currency Rates"),
    }

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence(type="sale_quot")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            if not num:
                return None
            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

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "number": _get_number,
        "currency_id": _get_currency,
        "tax_type": "tax_ex",
        "user_id": lambda self, context: get_active_user(),
        "uuid": lambda *a: str(uuid.uuid4()),
        "company_id": lambda *a: get_active_company(),
    }
    _constraints = ["check_fields"]
    _order = "date desc"

    def check_fields(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state in ("waiting_approval", "approved"):
                if not obj.lines:
                    raise Exception("No lines in quotation")

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

    def write(self, ids, vals, **kw):
        opport_ids = []
        for obj in self.browse(ids):
            if obj.opport_id:
                opport_ids.append(obj.opport_id.id)
        super().write(ids, vals, **kw)
        if opport_ids:
            get_model("sale.opportunity").function_store(opport_ids)
        self.function_store(ids)

    def function_store(self, ids, field_names=None, context={}):
        super().function_store(ids, field_names, context)
        opport_ids = []
        for obj in self.browse(ids):
            if obj.opport_id:
                opport_ids.append(obj.opport_id.id)
        if opport_ids:
            get_model("sale.opportunity").function_store(opport_ids)

    def get_amount(self, ids, context={}):
        res = {}
        for obj in self.browse(ids):
            vals = {}
            subtotal = 0
            tax = 0
            for line in obj.lines:
                if line.is_hidden:
                    continue
                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 or 0) - line_tax
                else:
                    subtotal += line.amount or 0
            vals["amount_subtotal"] = subtotal
            vals["amount_tax"] = tax
            vals["amount_total"] = subtotal + tax
            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 submit_for_approval(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "draft":
                raise Exception("Invalid state")
            obj.write({"state": "waiting_approval"})
        self.trigger(ids, "submit_for_approval")

    def approve(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state not in ("draft", "waiting_approval"):
                raise Exception("Invalid state")
            obj.write({"state": "approved"})

    def update_amounts(self, context):
        print("update_amounts")
        data = context["data"]
        data["amount_subtotal"] = 0
        data["amount_tax"] = 0
        tax_type = data["tax_type"]
        for line in data["lines"]:
            if not line:
                continue
            amt = (line.get("qty") or 0) * (line.get("unit_price") or 0)
            if line.get("discount"):
                disc = amt * line["discount"] / Decimal(100)
                amt -= disc
            else:
                disc = 0
            line["amount"] = amt
        hide_parents = []
        for line in data["lines"]:
            if not line:
                continue
            if line.get("sequence") and line.get("hide_sub"):
                hide_parents.append(line["sequence"])
        is_hidden = {}
        hide_totals = {}
        for line in data["lines"]:
            if not line:
                continue
            if not line.get("sequence"):
                continue
            parent_seq = None
            for seq in hide_parents:
                if line["sequence"].startswith(seq + "."):
                    parent_seq = seq
                    break
            if parent_seq:
                is_hidden[line["sequence"]] = True
                hide_totals.setdefault(parent_seq, 0)
                hide_totals[parent_seq] += line["amount"]
        for line in data["lines"]:
            if not line:
                continue
            if line.get("sequence") and line.get("hide_sub"):
                line["amount"] = hide_totals.get(line["sequence"], 0)
                if line["qty"]:
                    line["unit_price"] = line["amount"] / line["qty"]
        for line in data["lines"]:
            if is_hidden.get(line.get("sequence")):
                continue
            tax_id = line.get("tax_id")
            if tax_id:
                tax = get_model("account.tax.rate").compute_tax(
                    tax_id, line["amount"], tax_type=tax_type)
                data["amount_tax"] += tax
            else:
                tax = 0
            if tax_type == "tax_in":
                data["amount_subtotal"] += line["amount"] - tax
            else:
                data["amount_subtotal"] += line["amount"]
        data["amount_total"] = data["amount_subtotal"] + data["amount_tax"]
        return data

    def onchange_product(self, context):
        data = context["data"]
        contact_id = data.get("contact_id")
        if contact_id:
            contact = get_model("contact").browse(contact_id)
        else:
            contact = None
        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["est_margin_percent_input"] = prod.gross_profit
        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.sale_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.sale_tax_id is not None:
            line["tax_id"] = prod.sale_tax_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"]
        if line.get("unit_price") is None:
            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.sale_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 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.sale_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 onchange_uom(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)
        uom_id = line.get("uom_id")
        if not uom_id:
            return {}
        uom = get_model("uom").browse(uom_id)
        if prod.sale_price is not None:
            line[
                "unit_price"] = prod.sale_price * uom.ratio / prod.uom_id.ratio
        data = self.update_amounts(context)
        return data

    def copy(self, ids, context):
        obj = self.browse(ids)[0]
        vals = {
            "ref": obj.number,
            "contact_id": obj.contact_id.id,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "payment_terms": obj.payment_terms,
            "other_info": obj.other_info,
            "exp_date": obj.exp_date,
            "opport_id": obj.opport_id.id,
            "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,
                "discount": line.discount,
                "tax_id": line.tax_id.id,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals, context=context)
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "quot",
                "mode": "form",
                "active_id": new_id,
            },
            "flash":
            "Quotation %s copied from %s" % (new_obj.number, obj.number),
        }

    def revise(self, ids, context):
        obj = self.browse(ids)[0]
        res = self.copy(ids, context)
        obj.write({"state": "revised"})
        return res

    def copy_to_sale_order(self, ids, context):
        id = ids[0]
        obj = self.browse(id)
        sale_vals = {
            "ref": obj.number,
            "quot_id": obj.id,
            "contact_id": obj.contact_id.id,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "lines": [],
            "user_id": obj.user_id.id,
            "other_info": obj.other_info,
            "payment_terms": obj.payment_terms,
            "price_list_id": obj.price_list_id.id,
            "job_template_id": obj.job_template_id.id,
            "est_costs": [],
            "currency_rates": [],
        }
        for line in obj.lines:
            if not line.qty or not line.uom_id or not line.unit_price:
                continue
            prod = line.product_id
            line_vals = {
                "sequence": line.sequence,
                "product_id": prod.id,
                "description": line.description,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "unit_price": line.unit_price if not line.is_hidden else 0,
                "discount": line.discount if not line.is_hidden else 0,
                "tax_id": line.tax_id.id if not line.is_hidden else None,
                "location_id": prod.location_id.id if prod else None,
            }
            sale_vals["lines"].append(("create", line_vals))
        for cost in obj.est_costs:
            cost_vals = {
                "sequence": cost.sequence,
                "product_id": cost.product_id.id,
                "description": cost.description,
                "supplier_id": cost.supplier_id.id,
                "list_price": cost.list_price,
                "purchase_price": cost.purchase_price,
                "purchase_duty_percent": cost.purchase_duty_percent,
                "purchase_ship_percent": cost.purchase_ship_percent,
                "landed_cost": cost.landed_cost,
                "qty": cost.qty,
                "currency_id": cost.currency_id.id,
            }
            sale_vals["est_costs"].append(("create", cost_vals))
        for r in obj.currency_rates:
            rate_vals = {
                "currency_id": r.currency_id.id,
                "rate": r.rate,
            }
            sale_vals["currency_rates"].append(("create", rate_vals))
        sale_id = get_model("sale.order").create(sale_vals, context=context)
        sale = get_model("sale.order").browse(sale_id)
        return {
            "next": {
                "name": "sale",
                "mode": "form",
                "active_id": sale_id,
            },
            "flash":
            "Sale order %s created from quotation %s" %
            (sale.number, obj.number)
        }

    def do_won(self, ids, context={}):
        for obj in self.browse(ids):
            assert obj.state == "approved"
            obj.write({"state": "won"})

    def do_lost(self, ids, context={}):
        for obj in self.browse(ids):
            assert obj.state == "approved"
            obj.write({"state": "lost"})

    def do_reopen(self, ids, context={}):
        for obj in self.browse(ids):
            assert obj.state in ("won", "lost")
            obj.write({"state": "approved"})

    def get_state(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            state = obj.state
            if state == "approved":
                found = False
                for sale in obj.sales:
                    if sale.state in ("confirmed", "done"):
                        found = True
                        break
                if found:
                    state = "won"
            vals[obj.id] = state
        return vals

    def view_link(self, ids, context={}):
        obj = self.browse(ids)[0]
        uuid = obj.uuid
        dbname = get_active_db()
        return {
            "next": {
                "type": "url",
                "url": "/view_quot?dbname=%s&uuid=%s" % (dbname, uuid),
            }
        }

    def get_template_quot_form(self, ids, context={}):
        obj = self.browse(ids)[0]
        has_discount = False
        for line in obj.lines:
            if line.discount:
                has_discount = True
        if has_discount:
            return "quot_form_disc"
        else:
            return "quot_form"

    def to_draft(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.write({"state": "draft"})

    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 onchange_cost_product(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        if prod_id:
            prod = get_model("product").browse(prod_id)
            line["description"] = prod.name
            line["list_price"] = prod.purchase_price
            line["purchase_price"] = prod.purchase_price
            line["landed_cost"] = prod.landed_cost
            line["qty"] = 1
            line["uom_id"] = prod.uom_id.id
            line["currency_id"] = prod.purchase_currency_id.id
            line["purchase_duty_percent"] = prod.purchase_duty_percent
            line["purchase_ship_percent"] = prod.purchase_ship_percent
            line["landed_cost"] = prod.landed_cost
            line["purcase_price"] = prod.purchase_price

            if prod.suppliers:
                line["supplier_id"] = prod.suppliers[0].supplier_id.id
        return data

    def get_est_profit(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            cost = 0
            for line in obj.lines:
                cost += line.est_cost_amount or 0
            profit = (obj.amount_subtotal or 0) - cost
            margin = profit * 100 / obj.amount_subtotal if obj.amount_subtotal else None
            vals[obj.id] = {
                "est_cost_amount": cost,
                "est_profit_amount": profit,
                "est_margin_percent": margin,
            }
        return vals

    def create_est_costs(self, ids, context={}):
        obj = self.browse(ids[0])
        del_ids = []
        for cost in obj.est_costs:
            if cost.product_id:
                del_ids.append(cost.id)
        get_model("quot.cost").delete(del_ids)
        #obj.write({"est_costs":[("delete_all",)]})
        for line in obj.lines:
            prod = line.product_id
            if not prod:
                continue
            if not prod.purchase_price:
                continue
            if not line.sequence:
                continue
            if "bundle" == prod.type:
                continue
            vals = {
                "quot_id":
                obj.id,
                "sequence":
                line.sequence if not line.is_hidden else line.parent_sequence,
                "product_id":
                prod.id,
                "description":
                prod.name,
                "supplier_id":
                prod.suppliers[0].supplier_id.id if prod.suppliers else None,
                "list_price":
                prod.purchase_price,
                "purchase_price":
                prod.purchase_price,
                "landed_cost":
                prod.landed_cost,
                "purchase_duty_percent":
                prod.purchase_duty_percent,
                "purchase_ship_percent":
                prod.purchase_ship_percent,
                "qty":
                line.qty,
                "currency_id":
                prod.purchase_currency_id.id,
            }
            get_model("quot.cost").create(vals)

    def merge_quotations(self, ids, context={}):
        if len(ids) < 2:
            raise Exception("Can not merge less than two quotations")
        contact_ids = []
        currency_ids = []
        tax_types = []
        for obj in self.browse(ids):
            contact_ids.append(obj.contact_id.id)
            currency_ids.append(obj.currency_id.id)
            tax_types.append(obj.tax_type)
        contact_ids = list(set(contact_ids))
        currency_ids = list(set(currency_ids))
        tax_types = list(set(tax_types))
        if len(contact_ids) > 1:
            raise Exception("Quotation customers have to be the same")
        if len(currency_ids) > 1:
            raise Exception("Quotation currencies have to be the same")
        if len(tax_types) > 1:
            raise Exception("Quotation tax types have to be the same")
        vals = {
            "contact_id": contact_ids[0],
            "currency_id": currency_ids[0],
            "tax_type": tax_types[0],
            "lines": [],
            "est_costs": [],
        }
        seq = 0
        for obj in self.browse(ids):
            seq_map = {}
            for line in obj.lines:
                seq += 1
                seq_map[line.sequence] = seq
                line_vals = {
                    "sequence": seq,
                    "product_id": line.product_id.id,
                    "description": line.description,
                    "qty": line.qty,
                    "uom_id": line.uom_id.id,
                    "unit_price": line.unit_price,
                    "discount": line.discount,
                    "tax_id": line.tax_id.id,
                }
                vals["lines"].append(("create", line_vals))
            for cost in obj.est_costs:
                cost_vals = {
                    "sequence": seq_map.get(cost.sequence),
                    "product_id": cost.product_id.id,
                    "description": cost.description,
                    "supplier_id": cost.supplier_id.id,
                    "list_price": cost.list_price,
                    "purchase_price": cost.purchase_price,
                    "landed_cost": cost.landed_cost,
                    "qty": cost.qty,
                    "currency_id": cost.currency_id.id,
                }
                vals["est_costs"].append(("create", cost_vals))
        new_id = self.create(vals, context=context)
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "quot",
                "mode": "form",
                "active_id": new_id,
            },
            "flash": "Quotations merged",
        }

    def onchange_est_margin(self, context={}):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        margin = line["est_margin_percent_input"]
        amt = line["est_cost_amount"] / (1 - margin / Decimal(100))
        price = round(amt / line["qty"])
        line["unit_price"] = price
        self.update_amounts(context)
        return data

    def get_relative_currency_rate(self, ids, currency_id):
        obj = self.browse(ids[0])
        rate = None
        for r in obj.currency_rates:
            if r.currency_id.id == currency_id:
                rate = r.rate
                break
        if rate is None:
            rate_from = get_model("currency").get_rate([currency_id],
                                                       obj.date) or Decimal(1)
            rate_to = obj.currency_id.get_rate(obj.date) or Decimal(1)
            rate = rate_from / rate_to
        return rate
Пример #16
0
class Transform(Model):
    _name = "stock.transform"
    _string = "Transform"
    _name_field = "number"
    _fields = {
        "date": fields.Date("Date", required=True, search=True),
        "number": fields.Char("Number", required=True, search=True),
        "location_id": fields.Many2One("stock.location", "Location", condition=[["type", "=", "internal"]], search=True, required=True),
        "container_id": fields.Many2One("stock.container", "Container"),
        "state": fields.Selection([["draft", "Draft"], ["done", "Completed"], ["voided", "Voided"]], "Status", required=True),
        "related_id": fields.Reference([["sale.order", "Sales Order"], ["purchase.order", "Purchase Order"],["job", "Service Order"], ["product.claim", "Claim Bill"], ["product.borrow", "Borrow Request"], ["stock.picking", "Picking"]], "Related To"),
        "stock_moves": fields.One2Many("stock.move", "related_id", "Stock Movements"),
        "source_lines": fields.One2Many("stock.transform.source", "transform_id", "Source Lines"),
        "target_lines": fields.One2Many("stock.transform.target", "transform_id", "Target Lines"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "journal_id": fields.Many2One("stock.journal", "Journal"),
    }
    _order = "date,id"

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

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "number": _get_number,
    }

    def validate(self, ids, context={}):
        obj = self.browse(ids)[0]
        settings = get_model("settings").browse(1)
        res = get_model("stock.location").search([["type", "=", "transform"]])
        if not res:
            raise Exception("Missing transform location")
        trans_loc_id = res[0]
        move_ids = []

        for source in obj.source_lines:
            vals = {
                "journal_id": obj.journal_id.id or settings.transform_journal_id.id,
                "location_from_id": obj.location_id.id,
                "location_to_id": trans_loc_id,
                "container_from_id": source.container_id.id,
                "product_id": source.product_id.id,
                "qty": source.qty,
                "qty2": source.qty2,
                "uom_id": source.uom_id.id,
                "lot_id": source.lot_id.id if source.lot_id else None,
                "related_id": "stock.transform,%d" % obj.id,
            }
            move_id = get_model("stock.move").create(vals)
            move_ids.append(move_id)
        for target in obj.target_lines:
            vals = {
                "journal_id": obj.journal_id.id or settings.transform_journal_id.id,
                "location_from_id": trans_loc_id,
                "location_to_id": obj.location_id.id,
                "container_to_id": target.container_id.id,
                "product_id": target.product_id.id,
                "qty": target.qty,
                "qty2": target.qty2,
                "uom_id": target.uom_id.id,
                "lot_id": target.lot_id.id if target.lot_id else None,
                "related_id": "stock.transform,%d" % obj.id,
            }
            move_id = get_model("stock.move").create(vals)
            move_ids.append(move_id)
        get_model("stock.move").set_done(move_ids)
        obj.write({"state": "done"})

    def void(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.stock_moves.delete()
        obj.write({"state": "voided"})

    def to_draft(self, ids, context={}):
        obj = self.browse(ids)[0]
        obj.stock_moves.delete()
        obj.write({"state": "draft"})

    def onchange_from_product(self, context={}):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        prod = get_model("product").browse(prod_id)
        line["uom_id"] = prod.uom_id.id
        line["qty"] = 1
        return data

    def onchange_to_product(self, context={}):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        prod = get_model("product").browse(prod_id)
        line["uom_id"] = prod.uom_id.id
        line["qty"] = 1
        return data

    def onchange_container(self, context={}):
        data = context["data"]
        cont_id = data.get("container_id")
        location_id = data.get("location_id")
        if not cont_id or not location_id:
            return
        cont = get_model("stock.container").browse(cont_id)
        contents = cont.get_contents()
        lines = []
        for (prod_id, lot_id, loc_id), (qty, amt, qty2) in contents.items():
            if loc_id != location_id:
                continue
            prod = get_model("product").browse(prod_id)
            line_vals = {
                "product_id": prod_id,
                "lot_id": lot_id,
                "qty": qty,
                "uom_id": prod.uom_id.id,
                "qty2": qty2,
                "container_id": cont_id,
            }
            lines.append(line_vals)
        data["source_lines"] = lines
        return data
Пример #17
0
class Activity(Model):
    _name = "activity"
    _string = "Activity"
    _name_field = "subject"
    _fields = {
        "type":
        fields.Selection([["task", "Task"], ["event", "Event"],
                          ["meeting", "Meeting"], ["call", "Call"]],
                         "Activity Type",
                         required=True,
                         search=True),
        "user_id":
        fields.Many2One("base.user", "Assigned To", search=True,
                        required=True),
        "subject":
        fields.Char("Subject", required=True, size=128, search=True),
        "date":
        fields.Date("Date", search=True),
        "due_date":
        fields.Date("Due Date"),
        "description":
        fields.Text("Description"),
        "body":
        fields.Text("Body"),
        "state":
        fields.Selection(
            [["new", "Not Started"], ["in_progress", "In Progress"],
             ["done", "Completed"], ["waiting", "Waiting on someone else"],
             ["deferred", "Deferred"]],
            "Status",
            required=True),
        "priority":
        fields.Selection(
            [["high", "High"], ["normal", "Normal"], ["low", "Low"]],
            "Priority"),
        "phone":
        fields.Char("Phone"),
        "email":
        fields.Char("Email"),
        "event_start":
        fields.DateTime("Start"),
        "event_end":
        fields.DateTime("End"),
        "location":
        fields.Char("Location"),
        "email_uid":
        fields.Char("Email UID"),
        "email_account_id":
        fields.Many2One("email.account", "Email Account"),
        "work_times":
        fields.One2Many("work.time", "activity_id", "Work Time"),
        "related_id":
        fields.Reference(
            [["contact", "Contact"], ["sale.opportunity", "Opportunity"],
             ["sale.quot", "Quotation"], ["sale.order", "Sales Order"],
             ["job", "Service Order"], ["issue", "Issue"]], "Related To"),
        "name_id":
        fields.Reference([["contact", "Contact"], ["sale.lead", "Lead"]],
                         "Name"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "overdue":
        fields.Boolean("Overdue",
                       function="get_overdue",
                       function_search="search_overdue"),
    }

    def _get_name_id(self, context={}):
        defaults = context.get("defaults")
        if not defaults:
            return
        related = defaults.get("related_id")
        if not related:
            return
        model, model_id = related.split(",")
        model_id = int(model_id)
        if model == "sale.quot":
            obj = get_model("sale.quot").browse(model_id)
            return "contact,%s" % obj.contact_id.id

    _defaults = {
        "state": "new",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "type": lambda self, ctx: ctx.get("activ_type") or "task",
        "name_id": _get_name_id,
    }
    _order = "due_date desc,date desc,id desc"

    # XXX
    def view_activity(self, ids, context={}):
        obj = self.browse(ids[0])
        return {
            "next": {
                "name": "activ",
                "mode": "form",
                "active_id": obj.id,
            }
        }

    def send_email(self, ids, context={}):
        obj = self.browse(ids)[0]
        from_addr = obj.user_id.email
        to_addr = obj.name_id.email
        if not to_addr:
            raise Exception("Email not found")
        res = get_model("email.account").search([["type", "=", "smtp"]])
        if not res:
            raise Exception("Email account not found")
        acc_id = res[0]
        acc = get_model("email.account").browse(acc_id)
        server = smtplib.SMTP(acc.host, acc.port)
        if acc.user:
            server.login(acc.user, acc.password)
        msg = "From: " + from_addr + "\r\n"
        msg += "To: " + to_addr + "\r\n"
        msg += "Subject: " + obj.subject + "\r\n\r\n"
        msg += obj.body
        server.sendmail(from_addr, [to_addr], msg)
        obj.write({"state": "done"})
        server.quit()

    # XXX: move this
    def fetch_email(self, context={}):
        print("fetch_email")
        acc_ids = get_model("email.account").search([["type", "=", "pop3"]])
        for acc in get_model("email.account").browse(acc_ids):
            print("connecting %s %s %s" % (acc.host, acc.port, acc.user))
            if acc.security == "ssl":
                serv = poplib.POP3_SSL(acc.host, acc.port)
            else:
                serv = poplib.POP3(acc.host, acc.port)
            serv.user(acc.user)
            if acc.password:
                serv.pass_(acc.password)
            try:
                resp, msg_list, size = serv.uidl()
                print("%d messages" % len(msg_list))
                for msg_info in msg_list:
                    msg_no, msg_uid = msg_info.decode().split()
                    print("msg_no", msg_no)
                    print("msg_uid", msg_uid)
                    try:
                        res = self.search_read(
                            [["email_account_id", "=", acc.id],
                             ["email_uid", "=", msg_uid]])
                        if res:
                            print("skipping %s" % msg_uid)
                            serv.dele(msg_no)
                            continue
                        print("reading %s" % msg_uid)
                        resp, lines, size = serv.retr(msg_no)
                        msg = email.message_from_bytes(b"\n".join(lines))

                        def dec_header(data):
                            dh = decode_header(data or "")
                            s = ""
                            for data, charset in dh:
                                if isinstance(data, str):
                                    s += data
                                else:
                                    s += data.decode(
                                        conv_charset(charset) or "utf-8")
                            return s

                        def dec_date(data):
                            res = parsedate(data or "")
                            if not res:
                                return ""
                            return time.strftime("%Y-%m-%d %H:%M:%S", res)

                        def get_body(m):
                            if m.get_filename():
                                return "[Attachment: %s (%s)]\n" % (dec_header(
                                    m.get_filename()), m.get_content_type())
                            else:
                                if not m.is_multipart():
                                    charset = conv_charset(
                                        m.get_content_charset())
                                    return m.get_payload(decode=True).decode(
                                        charset or "utf-8", errors="replace")
                                else:
                                    data = m.get_payload()
                                    found = False
                                    res = []
                                    for m in data:
                                        fn = m.get_filename()
                                        if not fn:
                                            if found:
                                                continue
                                            found = True
                                        res.append(get_body(m))
                                    return "\n".join(res)

                        email_vals = {
                            "account_id": acc.id,
                            "msg_uid": msg_uid,
                            "date": dec_date(msg["Date"]),
                            "from_addr": parseaddr(msg["From"])[1][:64],
                            "to_addr": parseaddr(msg["To"])[1][:64],
                            "subject": dec_header(msg["Subject"])[:128],
                            "body": get_body(msg),
                        }
                        get_model("activity").import_email(email_vals)
                    except Exception as e:
                        print("WARNING: failed to import email %s", msg_uid)
                    finally:
                        serv.dele(msg_no)
            finally:
                print("quitting")
                db = database.get_connection()
                db.commit()
                serv.quit()

    # XXX: move this
    def import_email(self, email):
        print("import_email")
        print("from=%s to=%s subject=%s" %
              (email["from_addr"], email["to_addr"], email["subject"]))
        from_user_id = None
        from_contact_id = None
        from_lead_id = None
        to_user_id = None
        to_contact_id = None
        to_lead_id = None
        user_id = None
        contact_id = None
        lead_id = None
        contact_id = None
        opport_id = None
        quot_id = None
        sale_id = None
        name_id = None
        related_id = None
        res = get_model("base.user").search(
            [["email", "=ilike", email["from_addr"]]])
        if res:
            from_user_id = res[0]
        res = get_model("contact").search(
            [["email", "=ilike", email["from_addr"]]])
        if res:
            from_contact_id = res[0]
        else:
            res = get_model("sale.lead").search(
                [["email", "=ilike", email["from_addr"]]])
            if res:
                from_lead_id = res[0]
        if not from_user_id and not from_contact_id and not from_lead_id:
            print("  => skipping (from)")
            return
        res = get_model("base.user").search(
            [["email", "=ilike", email["to_addr"]]])
        if res:
            to_user_id = res[0]
        res = get_model("contact").search(
            [["email", "=ilike", email["to_addr"]]])
        if res:
            to_contact_id = res[0]
        else:
            res = get_model("sale.lead").search(
                [["email", "=ilike", email["to_addr"]]])
            if res:
                to_lead_id = res[0]
        if (from_contact_id or from_lead_id) and to_user_id:
            if from_contact_id:
                contact_id = from_contact_id
            if from_lead_id:
                lead_id = from_lead_id
            user_id = to_user_id
        elif from_user_id and (to_contact_id or to_lead_id):
            user_id = from_user_id
            if to_contact_id:
                contact_id = to_contact_id
            if to_lead_id:
                lead_id = to_lead_id
        else:
            print("  => skipping (from/to)")
            return
        if contact_id:
            contact = get_model("contact").browse(contact_id)
            if contact.contact_id:
                contact_id = contact.contact_id.id
        if contact_id and email["subject"]:
            subj = email["subject"].lower()
            opp_ids = get_model("sale.opportunity").search(
                [["contact_id", "=", contact_id], ["state", "=", "open"]])
            for opp in get_model("sale.opportunity").browse(opp_ids):
                if subj.find(opp.name.lower()) != -1:
                    opport_id = opp.id
                    break
            quot_ids = get_model("sale.quot").search(
                [["contact_id", "=", contact_id], ["state", "=", "approved"]])
            for quot in get_model("sale.quot").browse(quot_ids):
                if subj.find(quot.number.lower()) != -1:
                    quot_id = quot.id
                    break
            sale_ids = get_model("sale.order").search(
                [["contact_id", "=", contact_id], ["state", "=", "confirmed"]])
            for sale in get_model("sale.order").browse(sale_ids):
                if subj.find(sale.number.lower()) != -1:
                    sale_id = sale.id
                    break
        if contact_id:
            name_id = "contact,%d" % contact_id
        elif lead_id:
            name_id = "sale.lead,%d" % lead_id
        if opport_id:
            related_id = "sale.opportunity,%d" % opport_id
        elif quot_id:
            related_id = "sale.quot,%d" % quot_id
        elif sale_id:
            related_id = "sale.order,%d" % sale_id
        elif contact_id:
            related_id = "contact,%d" % contact_id
        vals = {
            "type": "email",
            "subject": email["subject"],
            "state": "done",
            "user_id": user_id,
            "related_id": related_id,
            "name_id": name_id,
            "email_uid": email["msg_uid"],
            "email_account_id": email["account_id"],
            "body": email["body"],
            "date": email["date"],
        }
        act_id = get_model("activity").create(vals)
        print("  => new activity %d" % act_id)
        return act_id

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

    def search_overdue(self, clause, context={}):
        return [["due_date", "<", time.strftime("%Y-%m-%d")],
                ["state", "!=", "done"]]

    def check_days_before_overdue(self,
                                  ids,
                                  days=None,
                                  days_from=None,
                                  days_to=None,
                                  context={}):
        print("Activity.check_days_before_overdue", ids, days, days_from,
              days_to)
        cond = [["state", "!=", "done"]]
        if days != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days)).strftime("%Y-%m-%d")
            cond.append(["due_date", "=", d])
        if days_from != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days_from)).strftime("%Y-%m-%d")
            print("XXXXXXXXXXXXXxx d", d)
            cond.append(["due_date", "<=", d])
        if days_to != None:
            d = (datetime.date.today() +
                 datetime.timedelta(days=days_to)).strftime("%Y-%m-%d")
            cond.append(["due_date", ">=", d])
        if ids:
            cond.append(["ids", "in", ids])
        ids = self.search(cond)
        return ids
Пример #18
0
class InvoiceLine(Model):
    _name = "account.invoice.line"
    _fields = {
        "invoice_id":
        fields.Many2One("account.invoice",
                        "Invoice",
                        required=True,
                        on_delete="cascade"),
        "product_id":
        fields.Many2One("product", "Product"),
        "description":
        fields.Text("Description", required=True),
        "qty":
        fields.Decimal("Qty"),
        "uom_id":
        fields.Many2One("uom", "UoM"),
        "unit_price":
        fields.Decimal("Unit Price", scale=6),
        "discount":
        fields.Decimal("Disc %"),  # XXX: rename to discount_percent later
        "discount_amount":
        fields.Decimal("Disc Amt"),
        "account_id":
        fields.Many2One("account.account",
                        "Account",
                        condition=[["type", "!=", "view"]]),
        "tax_id":
        fields.Many2One("account.tax.rate", "Tax Rate", on_delete="restrict"),
        "amount":
        fields.Decimal("Amount", required=True),
        "invoice_date":
        fields.Date("Invoice Date",
                    function="_get_related",
                    function_context={"path": "invoice_id.date"}),
        "invoice_contact_id":
        fields.Many2One("contact",
                        "Invoice Partner",
                        function="_get_related",
                        function_context={"path": "invoice_id.contact_id"}),
        "purch_id":
        fields.Many2One("purchase.order", "Purchase Order"),
        "track_id":
        fields.Many2One("account.track.categ",
                        "Track-1",
                        condition=[["type", "=", "1"]]),
        "track2_id":
        fields.Many2One("account.track.categ",
                        "Track-2",
                        condition=[["type", "=", "2"]]),
        "amount_discount":
        fields.Decimal("Discount", function="get_discount"),
        "related_id":
        fields.Reference([["sale.order", "Sales Order"],
                          ["purchase.order", "Purchase Order"],
                          ["production.order", "Production Order"],
                          ["project", "Project"], ["job", "Service Order"],
                          ["service.contract", "Service Contract"],
                          ["work.time", "Work Time"]], "Related To"),
        "sale_id":
        fields.Many2One("sale.order", "Sale Order"),
        "purchase_id":
        fields.Many2One("purchase.order", "Purchase Order"),
    }

    def create(self, vals, **kw):
        id = super(InvoiceLine, self).create(vals, **kw)
        sale_id = vals.get("sale_id")
        if sale_id:
            get_model("sale.order").function_store([sale_id])
        purch_id = vals.get("purch_id")
        if purch_id:
            get_model("purchase.order").function_store([purch_id])
        return id

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

    def delete(self, ids, **kw):
        sale_ids = []
        purch_ids = []
        for obj in self.browse(ids):
            if obj.sale_id:
                sale_ids.append(obj.sale_id.id)
            if obj.purch_id:
                purch_ids.append(obj.purch_id.id)
        super(InvoiceLine, self).delete(ids, **kw)
        if sale_ids:
            get_model("sale.order").function_store(sale_ids)
        if purch_ids:
            get_model("purchase.order").function_store(purch_ids)

    def get_discount(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = (obj.qty or 0) * (obj.unit_price or 0)
            if obj.discount:
                amt *= (1 - obj.discount / 100)
            if obj.discount_amount:
                amt -= obj.discount_amount
            vals[obj.id] = amt
        return vals
Пример #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
Пример #20
0
class Move(Model):
    _name = "stock.move"
    _string = "Stock Movement"
    _name_field = "number"
    _multi_company = True
    _key = ["company_id", "number"]
    _fields = {
        "ref":
        fields.Char("Ref", search=True),  # XXX: deprecated
        "product_id":
        fields.Many2One("product", "Product", required=True, search=True),
        "location_from_id":
        fields.Many2One("stock.location",
                        "From Location",
                        required=True,
                        search=True),
        "location_to_id":
        fields.Many2One("stock.location",
                        "To Location",
                        required=True,
                        search=True),
        "qty":
        fields.Decimal("Qty", required=True, scale=6),
        "uom_id":
        fields.Many2One("uom", "UoM", required=True),
        "picking_id":
        fields.Many2One("stock.picking", "Picking", on_delete="cascade"),
        "date":
        fields.DateTime("Date", required=True, search=True),
        "cost_price_cur":
        fields.Decimal("Cost Price (Cur)", scale=6),  # in picking currency
        "cost_price":
        fields.Decimal("Cost Price", scale=6),  # in company currency
        "cost_amount":
        fields.Decimal("Cost Amount"),  # in company currency
        "cost_fixed":
        fields.Boolean("Cost Fixed"),  # don't calculate cost
        "state":
        fields.Selection([("draft", "Draft"), ("pending", "Planned"),
                          ("approved", "Approved"), ("done", "Completed"),
                          ("voided", "Voided")],
                         "Status",
                         required=True),
        "stock_count_id":
        fields.Many2One("stock.count", "Stock Count"),
        "move_id":
        fields.Many2One("account.move", "Journal Entry"),
        "user_id":
        fields.Many2One("base.user", "User"),
        "contact_id":
        fields.Many2One("contact", "Contact"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "serial_no":
        fields.Char("Serial No.", search=True),  # XXX: deprecated
        "lot_id":
        fields.Many2One("stock.lot", "Lot / Serial Number"),
        "container_from_id":
        fields.Many2One("stock.container", "From Container"),
        "container_to_id":
        fields.Many2One("stock.container", "To Container"),
        "packaging_id":
        fields.Many2One("stock.packaging", "Packaging"),
        "num_packages":
        fields.Integer("# Packages"),
        "notes":
        fields.Text("Notes"),
        "qty2":
        fields.Decimal("Qty2"),
        "company_id":
        fields.Many2One("company", "Company"),
        "invoice_id":
        fields.Many2One("account.invoice", "Invoice"),
        "related_id":
        fields.Reference(
            [["sale.order", "Sales Order"],
             ["purchase.order", "Purchase Order"], ["job", "Service Order"],
             ["account.invoice", "Invoice"], ["pawn.loan", "Loan"]],
            "Related To"),
        "number":
        fields.Char("Number", required=True, search=True),
        "journal_id":
        fields.Many2One("stock.journal", "Journal", required=True,
                        search=True),
        "alloc_costs":
        fields.One2Many("landed.cost.alloc", "move_id", "Allocated Costs"),
        "alloc_cost_amount":
        fields.Decimal("Allocated Costs",
                       scale=6,
                       function="get_alloc_cost_amount"),
        "track_id":
        fields.Many2One("account.track.categ", "Track"),
        "parent_id":
        fields.Many2One("stock.move", "Parent"),
    }
    _order = "date desc,id desc"

    def _get_loc_from(self, context={}):
        print("_get_loc_from", context)
        data = context.get("data")
        settings = get_model("settings").browse(1)
        if data:
            journal_id = data.get("journal_id")
            if journal_id:
                journal = get_model("stock.journal").browse(journal_id)
                if journal.location_from_id:
                    return journal.location_from_id.id
        pick_type = context.get("pick_type")
        if pick_type == "in":
            journal = settings.pick_in_journal_id
        elif pick_type == "out":
            journal = settings.pick_out_journal_id
        elif pick_type == "internal":
            journal = settings.pick_internal_journal_id
        else:
            journal = None
        if journal and journal.location_from_id:
            return journal.location_from_id.id
        if pick_type != "in":
            return None
        res = get_model("stock.location").search([["type", "=", "supplier"]],
                                                 order="id")
        if not res:
            return None
        return res[0]

    def _get_loc_to(self, context={}):
        print("_get_loc_to", context)
        data = context.get("data")
        settings = get_model("settings").browse(1)
        if data:
            journal_id = data.get("journal_id")
            if journal_id:
                journal = get_model("stock.journal").browse(journal_id)
                if journal.location_to_id:
                    return journal.location_to_id.id
        pick_type = context.get("pick_type")
        pick_type = context.get("pick_type")
        if pick_type == "in":
            journal = settings.pick_in_journal_id
        elif pick_type == "out":
            journal = settings.pick_out_journal_id
        elif pick_type == "internal":
            journal = settings.pick_internal_journal_id
        else:
            journal = None
        if journal and journal.location_from_id:
            return journal.location_to_id.id
        if pick_type != "out":
            return None
        res = get_model("stock.location").search([["type", "=", "customer"]])
        if not res:
            return None
        return res[0]

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

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "location_from_id": _get_loc_from,
        "location_to_id": _get_loc_to,
        "cost_price": 0,
        "cost_amount": 0,
        "company_id": lambda *a: get_active_company(),
        "number": _get_number,
    }

    def create(self, vals, context={}):
        pick_id = vals.get("picking_id")
        if pick_id:
            pick = get_model("stock.picking").browse(pick_id)
            vals["date"] = pick.date
            vals["picking_id"] = pick.id
            vals["journal_id"] = pick.journal_id.id
        new_id = super().create(vals, context=context)
        self.function_store([new_id])
        prod_id = vals["product_id"]
        user_id = get_active_user()
        set_active_user(1)
        get_model("product").write([prod_id], {"update_balance": True})
        set_active_user(user_id)
        return new_id

    def write(self, ids, vals, context={}):
        prod_ids = []
        for obj in self.browse(ids):
            prod_ids.append(obj.product_id.id)
        super().write(ids, vals, context=context)
        prod_id = vals.get("product_id")
        if prod_id:
            prod_ids.append(prod_id)
        self.function_store(ids)
        user_id = get_active_user()
        set_active_user(1)
        get_model("product").write(prod_ids, {"update_balance": True})
        set_active_user(user_id)

    def delete(self, ids, **kw):
        prod_ids = []
        for obj in self.browse(ids):
            prod_ids.append(obj.product_id.id)
        super().delete(ids, **kw)
        user_id = get_active_user()
        set_active_user(1)
        get_model("product").write(prod_ids, {"update_balance": True})
        set_active_user(user_id)

    def view_stock_transaction(self, ids, context={}):
        obj = self.browse(ids[0])
        if obj.picking_id:
            pick = obj.picking_id
            if pick.type == "in":
                next = {
                    "name": "pick_in",
                    "mode": "form",
                    "active_id": pick.id,
                }
            elif pick.type == "out":
                next = {
                    "name": "pick_out",
                    "mode": "form",
                    "active_id": pick.id,
                }
            elif pick.type == "internal":
                next = {
                    "name": "pick_internal",
                    "mode": "form",
                    "active_id": pick.id,
                }
        elif obj.stock_count_id:
            next = {
                "name": "stock_count",
                "mode": "form",
                "active_id": obj.stock_count_id.id,
            }
        else:
            raise Exception("Invalid stock move")
        return {"next": next}

    def set_done(self, ids, context={}):
        print("stock_move.set_done", ids)
        settings = get_model("settings").browse(1)
        prod_ids = []
        self.write(ids, {"state": "done"}, context=context)
        for obj in self.browse(ids):
            prod = obj.product_id
            prod_ids.append(prod.id)
            pick = obj.picking_id
            vals = {}
            if not obj.qty2 and prod.qty2_factor:
                qty2 = get_model("uom").convert(
                    obj.qty, obj.uom_id.id, prod.uom_id.id) * prod.qty2_factor
                vals["qty2"] = qty2
            elif prod.require_qty2 and obj.qty2 is None:
                raise Exception("Missing secondary qty for product %s" %
                                prod.code)
            if pick and pick.related_id and not obj.related_id:
                vals["related_id"] = "%s,%d" % (pick.related_id._model,
                                                pick.related_id.id)
            if pick and not pick.related_id and not obj.related_id:
                vals["related_id"] = "%s,%d" % (pick._model, pick.id)
            if obj.location_from_id.type == "view":
                raise Exception("Source location '%s' is a view location" %
                                obj.location_from_id.name)
            if obj.location_to_id.type == "view":
                raise Exception(
                    "Destination location '%s' is a view location" %
                    obj.location_to_id.name)
            if prod.require_lot and not obj.lot_id:
                raise Exception("Missing lot for product %s" % prod.code)
            if vals:
                obj.write(vals=vals, context=context)
            # change state in borrow requests # XXX: remove this
            if not obj.related_id:
                if pick.related_id._model == "product.borrow":
                    if pick.related_id.is_return_item:
                        pick.related_id.write({"state": "done"})
            elif obj.related_id._model == "product.borrow":
                if obj.related_id.is_return_item:
                    obj.related_id.write({"state": "done"})
        prod_ids = list(set(prod_ids))
        if prod_ids and settings.stock_cost_auto_compute:
            get_model("stock.compute.cost").compute_cost(
                [], context={"product_ids": prod_ids})
        if settings.stock_cost_mode == "perpetual" and not context.get(
                "no_post"):
            self.post(ids, context=context)
        self.update_lots(ids, context=context)
        self.set_reference(ids, context=context)
        self.check_periods(ids, context=context)
        print("<<<  stock_move.set_done")

    def check_periods(self, ids, context={}):
        for obj in self.browse(ids):
            d = obj.date[:10]
            res = get_model("stock.period").search([["date_from", "<=", d],
                                                    ["date_to", ">=", d],
                                                    ["state", "=", "posted"]])
            if res:
                raise Exception(
                    "Failed to validate stock movement because stock period already posted"
                )

    def set_reference(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.ref or not obj.related_id:
                continue
            ref = obj.related_id.name_get()[0][1]
            obj.write({"ref": ref})

    def reverse(self, ids, context={}):
        move_ids = []
        for obj in self.browse(ids):
            if obj.state != "done":
                raise Exception(
                    "Failed to reverse stock movement: invalid state")
            vals = {
                "journal_id":
                obj.journal_id.id,
                "product_id":
                obj.product_id.id,
                "qty":
                obj.qty,
                "uom_id":
                obj.uom_id.id,
                "location_from_id":
                obj.location_to_id.id,
                "location_to_id":
                obj.location_from_id.id,
                "cost_price_cur":
                obj.cost_price_cur,
                "cost_price":
                obj.cost_price,
                "cost_amount":
                obj.cost_amount,
                "qty2":
                obj.qty2,
                "ref":
                "Reverse: %s" % obj.ref if obj.ref else None,
                "related_id":
                "%s,%s" % (obj.related_id._model, obj.related_id.id)
                if obj.related_id else None,
                "picking_id":
                obj.picking_id.id,
            }
            move_id = self.create(vals)
            move_ids.append(move_id)
        self.set_done(move_ids)

    def get_alloc_cost_amount(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            amt = 0
            for alloc in obj.alloc_costs:
                if alloc.landed_id.state != "posted":
                    continue
                amt += alloc.amount or 0
            vals[obj.id] = amt
        return vals

    def post(self, ids, context={}):
        print("stock.move post", ids)
        accounts = {}
        post_date = None
        pick_ids = []
        for move in self.browse(ids):
            if move.move_id:
                raise Exception(
                    "Journal entry already create for stock movement %s" %
                    move.number)
            date = move.date[:10]
            if post_date is None:
                post_date = date
            else:
                if date != post_date:
                    raise Exception(
                        "Failed to post stock movements because they have different dates"
                    )
            prod = move.product_id
            #desc="[%s] %s @ %s %s "%(prod.code,prod.name,round(move.qty,2),move.uom_id.name) # XXX: too many lines in JE
            desc = "Inventory costing"
            acc_from_id = move.location_from_id.account_id.id
            if move.location_from_id.type == "customer":
                if prod.cogs_account_id:
                    acc_from_id = prod.cogs_account_id.id
                elif prod.categ_id and prod.categ_id.cogs_account_id:
                    acc_from_id = prod.categ_id.cogs_account_id.id
            elif move.location_from_id.type == "internal":
                if prod.stock_account_id:
                    acc_from_id = prod.stock_account_id.id
                elif prod.categ_id and prod.categ_id.stock_account_id:
                    acc_from_id = prod.categ_id.stock_account_id.id
            if not acc_from_id:
                raise Exception(
                    "Missing input account for stock movement %s (date=%s, ref=%s, product=%s)"
                    % (move.id, move.date, move.ref, prod.name))
            acc_to_id = move.location_to_id.account_id.id
            if move.location_to_id.type == "customer":
                if prod.cogs_account_id:
                    acc_to_id = prod.cogs_account_id.id
                elif prod.categ_id and prod.categ_id.cogs_account_id:
                    acc_to_id = prod.categ_id.cogs_account_id.id
            elif move.location_to_id.type == "internal":
                if prod.stock_account_id:
                    acc_to_id = prod.stock_account_id.id
                elif prod.categ_id and prod.categ_id.stock_account_id:
                    acc_to_id = prod.categ_id.stock_account_id.id
            if not acc_to_id:
                raise Exception(
                    "Missing output account for stock movement %s (date=%s, ref=%s, product=%s)"
                    % (move.id, move.date, move.ref, prod.name))
            track_from_id = move.location_from_id.track_id.id
            track_to_id = move.track_id.id or move.location_to_id.track_id.id  # XXX
            amt = move.cost_amount or 0
            if not move.move_id:  # XXX: avoid create double journal entry for LC for ex
                accounts.setdefault((acc_from_id, track_from_id, desc), 0)
                accounts.setdefault((acc_to_id, track_to_id, desc), 0)
                accounts[(acc_from_id, track_from_id, desc)] -= amt
                accounts[(acc_to_id, track_to_id, desc)] += amt
            if move.picking_id:
                pick_ids.append(move.picking_id.id)
        lines = []
        for (acc_id, track_id, desc), amt in accounts.items():
            lines.append({
                "description": desc,
                "account_id": acc_id,
                "track_id": track_id,
                "debit": amt > 0 and amt or 0,
                "credit": amt < 0 and -amt or 0,
            })
        vals = {
            "narration": "Inventory costing",
            "date": post_date,
            "lines": [("create", vals) for vals in lines],
        }
        pick_ids = list(set(pick_ids))
        if len(pick_ids) == 1:
            vals["related_id"] = "stock.picking,%s" % pick_ids[0]
        move_id = get_model("account.move").create(vals)
        get_model("account.move").post([move_id])
        get_model("stock.move").write(ids, {"move_id": move_id})
        return move_id

    def to_draft(self, ids, context={}):
        move_ids = []
        for obj in self.browse(ids):
            if obj.move_id:
                move_ids.append(obj.move_id.id)
        move_ids = list(set(move_ids))
        for move in get_model("account.move").browse(move_ids):
            move.void()
            move.delete()
        self.write(ids, {"state": "draft"})
        # change state in borrow requests
        for obj in self.browse(ids):
            if obj.related_id._model == "product.borrow":
                if not obj.related_id.is_return_item:
                    obj.related_id.write({"state": "approved"})

    def update_lots(self, ids, context={}):
        for obj in self.browse(ids):
            lot = obj.lot_id
            if not lot:
                continue
            if obj.location_from_id.type != "internal" and obj.location_to_id.type == "internal":
                lot.write({"received_date": obj.date})

    # XXX
    def get_unit_price(self, ids, context={}):
        settings = get_model("settings").browse(1)
        vals = {}
        for obj in self.browse(ids):
            pick = obj.picking_id
            if pick:
                if pick.currency_rate:
                    currency_rate = pick.currency_rate
                else:
                    if pick.currency_id.id == settings.currency_id.id:
                        currency_rate = 1
                    else:
                        rate_from = pick.currency_id.get_rate(date=pick.date)
                        if not rate_from:
                            raise Exception("Missing currency rate for %s" %
                                            pick.currency_id.code)
                        rate_to = settings.currency_id.get_rate(date=pick.date)
                        if not rate_to:
                            raise Exception("Missing currency rate for %s" %
                                            settings.currency_id.code)
                        currency_rate = rate_from / rate_to
                price = obj.unit_price_cur or 0
                price_conv = get_model("currency").convert(
                    price,
                    pick.currency_id.id,
                    settings.currency_id.id,
                    rate=currency_rate)
            else:
                price_conv = None
            vals[obj.id] = price_conv
        return vals
Пример #21
0
class Task(Model):
    _name = "task"
    _string = "Task"
    _name_field = "title"
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "sequence":
        fields.Integer("Sequence"),
        "date_created":
        fields.DateTime("Date Created", required=True, search=True),
        "project_id":
        fields.Many2One("project", "Project", required=True, search=True),
        "milestone_id":
        fields.Many2One("project.milestone", "Milestone", search=True),
        "task_list_id":
        fields.Many2One("task.list", "Task List", search=True),
        "job_id":
        fields.Many2One("job", "Service Order", search=True),
        "contact_id":
        fields.Many2One("contact",
                        "Customer",
                        function="_get_related",
                        function_context={"path": "project_id.contact_id"}),
        "title":
        fields.Char("Title", required=True, search=True),
        "description":
        fields.Text("Description", search=True),
        "progress":
        fields.Integer("Progress (%)"),
        "date_start":
        fields.Date("Start Date", required=True),
        "date_end":
        fields.Date("End Date", function="get_end_date", store=True),
        "duration":
        fields.Integer("Duration (Days)", required=True),
        "due_date":
        fields.Date("Due Date"),
        "done_date":
        fields.Date("Completion Date"),
        "resource_id":
        fields.Many2One("service.resource", "Assigned To"),  # XXX: deprecated
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "comments":
        fields.Text("Comments"),
        "messages":
        fields.One2Many("message", "related_id", "Messages"),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "state":
        fields.Selection([["open", "Open"], ["closed", "Closed"]],
                         "Status",
                         required=True,
                         search=True),
        "depends":
        fields.One2Many("task.depend", "task_id", "Task Dependencies"),
        "related_id":
        fields.Reference([["job", "Job"]], "Related To"),
        "depends_json":
        fields.Text("Task Dependencies (String)", function="get_depends_json"),
        "assignments":
        fields.One2Many("task.assign", "task_id", "Assignments"),
    }
    _order = "project_id.start_date,milestone_id.plan_date_from,task_list_id.date_created,date_start,id"  # XXX

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

    _defaults = {
        "date_created": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"),
        "state": "new",
        "number": _get_number,
    }

    def get_depends_json(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            res = []
            for dep in obj.depends:
                res.append((dep.id, dep.prev_task_id.id, dep.delay))
            vals[obj.id] = res
        return vals

    def calc_end_date(self, date_start, duration):
        d = datetime.strptime(date_start, "%Y-%m-%d")
        dur = 0
        while True:
            if not is_holiday(d):
                dur += 1
            if dur >= duration:
                break
            d += timedelta(days=1)
        return d.strftime("%Y-%m-%d")

    def update_end(self, context={}):
        data = context.get("data", {})
        date_start = data["date_start"]
        duration = data.get("duration", 0)
        data["date_end"] = self.calc_end_date(date_start, duration)
        return data

    def get_end_date(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = self.calc_end_date(obj.date_start, obj.duration)
        return vals

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

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

    def add_link(self, source_id, target_id, context={}):
        vals = {
            "prev_task_id": source_id,
            "task_id": target_id,
        }
        get_model("task.depend").create(vals)

    def delete_link(self, link_ids, context={}):
        get_model("task.depend").delete(link_ids)
Пример #22
0
class ReportStockMove(Model):
    _name = "report.stock.move"
    _transient = True
    _fields = {
        "pick_type":
        fields.Selection(
            [["in", "Goods Receipt"], ["internal", "Goods Transfer"],
             ["out", "Goods Issue"]],
            "Type",
            required=True),
        "date_from":
        fields.Date("Date From"),
        "date_to":
        fields.Date("Date To"),
        "location_from_id":
        fields.Many2One("stock.location", "Location From"),
        "location_to_id":
        fields.Many2One("stock.location", "Location To"),
        "ref":
        fields.Char("Ref"),
        "related_id":
        fields.Reference(
            [["sale.order", "Sales Order"],
             ["purchase.order", "Purchase Order"], ["job", "Service Order"],
             ["account.invoice", "Invoice"]], "Related To"),
        "show_loss_only":
        fields.Boolean("Show Loss Qty Only"),
    }

    def default_get(self, field_names=None, context={}, **kw):
        pick_type = context.get("pick_type")
        defaults = context.get("defaults", {})
        date_from = defaults.get("date_from")
        date_to = defaults.get("date_to")
        if pick_type:
            defaults["pick_type"] = pick_type
        if not date_from and not date_to:
            date_from = date.today().strftime("%Y-%m-01")
            date_to = (date.today() +
                       relativedelta(day=31)).strftime("%Y-%m-%d")
            defaults["date_from"] = date_from
            defaults["date_to"] = date_to
        elif not date_from and date_to:
            date_from = get_model("settings").get_fiscal_year_start(
                date=date_to)
            defaults["date_from"] = date_from
        return defaults

    def get_report_data(self, ids, context={}):
        if ids:
            params = self.read(ids, load_m2o=False)[0]
        else:
            params = self.default_get(load_m2o=False, context=context)
        cond = []
        if params.get("date_from"):
            date_from = params.get("date_from") + " 00:00:00"
            cond.append(["date", ">=", date_from])
        if params.get("date_to"):
            date_to = params.get("date_to") + " 23:59:59"
            cond.append(["date", "<=", date_to])
        if params.get("location_from_id"):
            cond.append(
                ["location_from_id", "=",
                 params.get("location_from_id")])
        if params.get("location_to_id"):
            cond.append(["location_to_id", "=", params.get("location_to_id")])
        if params.get("ref"):
            cond.append(["ref", "ilike", params.get("ref")])
        if params.get("related_id"):
            cond.append(["related_id", "=", params.get("related_id")])
        pick_type = params.get("pick_type")
        cond.append(["picking_id.type", "=", pick_type])
        move_list = get_model("stock.move").search_browse(cond)
        lines = []
        item_no = 0
        loss_loc_id = None
        loss_loc_ids = get_model("stock.location").search(
            [["type", "=", "inventory"]])
        if loss_loc_ids:
            loss_loc_id = loss_loc_ids[0]
        for move in move_list:
            qty_loss = 0
            if loss_loc_id:
                loss_cri = []
                loss_cri.append(["picking_id", "=", move.picking_id.id])
                loss_cri.append(["product_id", "=", move.product_id.id])
                loss_cri.append(
                    ["location_from_id", "=", move.location_from_id.id])
                loss_cri.append(
                    ["container_from_id", "=", move.container_from_id.id])
                loss_cri.append(["location_to_id", "=", loss_loc_id])
                loss_moves = get_model("stock.move").search_browse(loss_cri)
                for loss_move in loss_moves:
                    qty_loss += loss_move.qty

            if params.get("show_loss_only") and not qty_loss:
                continue
            item_no += 1
            line = self.get_line_data(context={"move": move})
            line["_item_no"] = item_no
            line["qty_loss"] = round(qty_loss, 2)
            lines.append(line)
        title = ""
        if pick_type == "in":
            title = "Goods Receive Report"
        elif pick_type == "out":
            title = "Goods Issue Report"
        elif pick_type == "internal":
            title = "Goods Transfer Report"
        return {"title": title, "lines": lines}

    def get_line_data(self, context={}):
        obj = context["move"]
        state = {
            "draft": "Draft",
            "pending": "Planned",
            "approved": "Approved",
            "done": "Completed",
            "voided": "Voided",
        }
        return {
            "number": obj.picking_id.number,
            "date": obj.date,
            "related": obj.related_id.number,
            "product_code": obj.product_id.code,
            "product_name": obj.product_id.name,
            "location_from": obj.location_from_id.name,
            "qty": obj.qty,
            "uom": obj.uom_id.name,
            "location_to": obj.location_to_id.name,
            "qty2": obj.qty2,
            "container_from": obj.container_from_id.number,
            "container_to": obj.container_to_id.number,
            "lot": obj.lot_id.number,
            "state": state[obj.state],
            "ref": obj.picking_id.ref,
        }
Пример #23
0
class Document(Model):
    _name = "document"
    _string = "Document"
    _audit_log = True
    _fields = {
        "file":
        fields.File("File"),
        "categ_id":
        fields.Many2One("document.categ", "Category", search=True),
        "description":
        fields.Text("Description", search=True),
        "contact_id":
        fields.Many2One("contact", "Contact", search=True),
        "related_id":
        fields.Reference(
            [["sale.quot", "Quotation"], ["sale.order", "Sales Order"],
             ["purchase.order", "Purchase Order"], ["job", "Service Order"],
             ["project", "Project"], ["hr.employee", "Employee"],
             ["account.invoice", "Invoice"], ["account.payment", "Payment"],
             ["account.track.categ", "Tracking Category"]], "Related To"),
        "date":
        fields.Date("Date Created", required=True, search=True),
        "attachments":
        fields.One2Many("attach", "related_id", "Attachments"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "expiry_date":
        fields.Date("Expiry Date", search=True),
        "expiring_soon":
        fields.Boolean("Expiring Soon",
                       store=False,
                       function_search="search_expiring"),
        "expired":
        fields.Boolean("Expired",
                       function="get_expired",
                       function_search="search_expired"),
        "create_job":
        fields.Boolean("Automatically Create Job To Renew"),  # XXX: deprecated
        "active":
        fields.Boolean("Active"),
        "days_remaining":
        fields.Integer("Days Remaining", function="get_days_remaining"),
        "reminders":
        fields.One2Many("reminder", "doc_id", "Reminders"),
        "state":
        fields.Selection([["draft", "Draft"], ["verified", "Verified"]],
                         "Status"),
        "share":
        fields.Boolean("Share With Contact"),
    }
    _order = "date desc"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def delete(self, ids, **kw):
        files = []
        for obj in self.browse(ids):
            if obj.file:
                files.append(obj.file)
        super().delete(ids, **kw)
        for f in files:
            path = utils.get_file_path(f)
            os.remove(path)
Пример #24
0
class Move(Model):
    _name = "account.move"
    _string = "Journal Entry"
    _name_field = "number"
    _multi_company = True
    _audit_log = True
    _key = ["company_id", "number"]
    _fields = {
        "journal_id":
        fields.Many2One("account.journal",
                        "Journal",
                        required=True,
                        search=True),
        "narration":
        fields.Text("Narration", required=True, search=True),
        "date":
        fields.Date("Document Date", required=True, search=True, index=True),
        "date_posted":
        fields.Date("Posted Date", search=True, index=True),
        "state":
        fields.Selection(
            [["draft", "Draft"], ["posted", "Posted"], ["voided", "Voided"]],
            "Status",
            required=True,
            search=True),
        "lines":
        fields.One2Many("account.move.line", "move_id", "Lines"),
        "total_debit":
        fields.Decimal("Total Debit",
                       function="get_total",
                       function_multi=True),
        "total_credit":
        fields.Decimal("Total Credit",
                       function="get_total",
                       function_multi=True),
        "type":
        fields.Selection([["auto", "auto"], ["manual", "Manual"]], "Type"),
        "ref":
        fields.Char("Reference", search=True),
        "number":
        fields.Char("Number", search=True, required=True),
        "default_line_desc":
        fields.Boolean("Default narration to line description"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "related_id":
        fields.Reference(
            [["account.invoice", "Invoice"], ["account.payment", "Payment"],
             ["account.transfer", "Transfer"], ["hr.expense", "Expense Claim"],
             ["service.contract", "Service Contract"], ["pawn.loan", "Loan"],
             ["landed.cost", "Landed Cost"],
             ["stock.picking", "Stock Picking"]], "Related To"),
        "company_id":
        fields.Many2One("company", "Company"),
        "track_entries":
        fields.One2Many("account.track.entry", "move_id", "Tracking Entries"),
        "difference":
        fields.Float("Difference",
                     function="get_difference",
                     function_multi=True),
    }

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

    def _get_number(self, context={}):
        journal_id = context.get("journal_id")
        if not journal_id:
            settings = get_model("settings").browse(1)
            journal_id = settings.general_journal_id.id
        if not journal_id:
            return
        journal = get_model("account.journal").browse(journal_id)
        seq_id = journal.sequence_id.id
        if not seq_id:
            return
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            res = self.search([["number", "=", num]])
            if not res:
                return num
            get_model("sequence").increment_number(seq_id, context=context)

    _defaults = {
        "state": "draft",
        "default_line_desc": True,
        "type": "auto",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "journal_id": _get_journal,
        "number": _get_number,
        "company_id": lambda *a: get_active_company(),
    }
    _order = "date desc,id desc"

    def get_difference(self, ids, context):
        vals = {}
        for obj in self.browse(ids):
            vals[obj.id] = {
                "difference": obj.total_debit - obj.total_credit,
            }
        return vals

    def create(self, vals, **kw):
        t0 = time.time()
        new_id = super().create(vals, **kw)
        t01 = time.time()
        dt01 = (t01 - t0) * 1000
        print("account_move.dt01", dt01)
        obj = self.browse([new_id])[0]
        line_ids = []
        rec_ids = []
        for line in obj.lines:
            line_ids.append(line.id)
            if line.reconcile_id:
                rec_ids.append(line.reconcile_id.id)
        get_model("account.move.line").function_store(line_ids)
        if rec_ids:
            get_model("account.reconcile").function_store(rec_ids)
        get_model("field.cache").clear_cache(model="account.account")
        t1 = time.time()
        dt = (t1 - t0) * 1000
        print("account_move.create <<< %d ms" % dt)
        return new_id

    def write(self, ids, vals, **kw):
        super().write(ids, vals, **kw)
        line_ids = []
        rec_ids = []
        for obj in self.browse(ids):
            for line in obj.lines:
                line_ids.append(line.id)
                if line.reconcile_id:
                    rec_ids.append(line.reconcile_id.id)
        get_model("account.move.line").function_store(line_ids)
        if rec_ids:
            get_model("account.reconcile").function_store(rec_ids)
            move_ids = []
            for rec in get_model("account.reconcile").browse(rec_ids):
                for line in rec.lines:
                    move_ids.append(line.move_id.id)
            move_ids = list(set(move_ids))
            inv_ids = get_model("account.invoice").search(
                [["move_id", "in", move_ids]])
            if inv_ids:
                get_model("account.invoice").function_store(
                    inv_ids)  # XXX: check this
        get_model("field.cache").clear_cache(model="account.account")

    def delete(self, ids, **kw):
        rec_ids = []
        for obj in self.browse(ids):
            if obj.state == "posted":
                raise Exception("Can not deleted posted journal entry")
            for line in obj.lines:
                if line.reconcile_id:
                    rec_ids.append(line.reconcile_id.id)
        super().delete(ids, **kw)
        if rec_ids:
            get_model("account.reconcile").function_store(rec_ids)
        get_model("field.cache").clear_cache(model="account.account")

    def post(self, ids, context={}):
        settings = get_model("settings").browse(1)
        for obj in self.browse(ids):
            if settings.lock_date:
                assert obj.date >= settings.lock_date, "Accounting transaction is before lock date"
            if obj.state != "draft":
                raise Exception("Journal entry is not draft")
            total_debit = 0
            total_credit = 0
            for line in obj.lines:
                acc = line.account_id
                if acc.type == "view":
                    raise Exception(
                        "Can not post to 'view' account ([%s] %s)" %
                        (acc.code, acc.name))
                if acc.company_id.id != obj.company_id.id:
                    raise Exception(
                        "Wrong company for account %s in journal entry %s (account company: %s, journal entry company %s)("
                        % (acc.code, obj.number, acc.company_id.code,
                           obj.company_id.code))
                if acc.require_contact and not line.contact_id:
                    raise Exception("Missing contact for account %s" %
                                    acc.code)
                if acc.require_tax_no and not line.tax_no:
                    raise Exception("Missing tax number for account %s" %
                                    acc.code)
                if acc.require_track and not line.track_id:
                    raise Exception(
                        "Missing tracking category for account %s" % acc.code)
                if acc.require_track2 and not line.track2_id:
                    raise Exception(
                        "Missing secondary tracking category for account %s" %
                        acc.code)
                if line.debit < 0:
                    raise Exception("Debit amount is negative (%s)" %
                                    line.debit)
                if line.credit < 0:
                    raise Exception("Credit amount is negative (%s)" %
                                    line.credit)
                if line.debit > 0 and line.credit > 0:
                    raise Exception(
                        "Debit and credit amounts can not be both non-zero (account: %s, debit: %s, credit: %s)"
                        % (line.account_id.name_get()[0][1], line.debit,
                           line.credit))
                total_debit += line.debit
                total_credit += line.credit
                if line.tax_comp_id and not line.tax_date:
                    line.write({"tax_date": line.move_id.date})
                if acc.currency_id.id != settings.currency_id.id and line.amount_cur is None:
                    raise Exception("Missing currency amount for account %s" %
                                    line.account_id.name_get()[0][1])
                if line.amount_cur is not None and acc.currency_id.id == settings.currency_id.id:
                    raise Exception(
                        "Currency amount for account %s should be empty" %
                        line.account_id.name_get()[0][1])
                if line.amount_cur is not None and line.amount_cur < 0:
                    raise Exception("Currency amount is negative (%s)" %
                                    line.amount_cur)
            if abs(total_debit - total_credit) != 0:
                print("NOT BALANCED total_debit=%s total_credit=%s" %
                      (total_debit, total_credit))
                for line in obj.lines:
                    print("  ACC: [%s] %s DR: %s CR: %s" %
                          (line.account_id.code, line.account_id.name,
                           line.debit, line.credit))
                raise Exception(
                    "Journal entry is not balanced (debit=%s, credit=%s)" %
                    (total_debit, total_credit))
            obj.write({"state": "posted"})
            if not obj.date_posted:
                date_posted = time.strftime("%Y-%m-%d")
                obj.write({"date_posted": date_posted})
            obj.create_track_entries()
            seq = 1
            for line in obj.lines:
                line.write({"sequence": seq})  # XXX
                seq += 1
        if not context.get("no_reconcile"):
            bank_ids = []
            for obj in self.browse(ids):
                for line in obj.lines:
                    acc = line.account_id
                    if acc.type in ("bank", "cash", "cheque"):
                        bank_ids.append(acc.id)
            if bank_ids:
                bank_ids = list(set(bank_ids))
                get_model("account.account").auto_bank_reconcile(bank_ids)
        get_model("account.balance").update_balances()

    def create_track_entries(self, ids, context={}):
        obj = self.browse(ids[0])
        settings = get_model("settings").browse(1)
        for line in obj.lines:
            if line.track_id:
                amt = line.credit - line.debit
                if line.track_id.currency_id:
                    amt = get_model("currency").convert(
                        amt, settings.currency_id.id,
                        line.track_id.currency_id.id)
                vals = {
                    "date": obj.date,
                    "track_id": line.track_id.id,
                    "amount": amt,
                    "description": line.description,
                    "move_id": obj.id,
                }
                get_model("account.track.entry").create(vals)
            if line.track2_id:
                amt = line.credit - line.debit
                if line.track2_id.currency_id:
                    amt = get_model("currency").convert(
                        amt, settings.currency_id.id,
                        line.track2_id.currency_id.id)
                vals = {
                    "date": obj.date,
                    "track_id": line.track2_id.id,
                    "amount": amt,
                    "description": line.description,
                    "move_id": obj.id,
                }
                get_model("account.track.entry").create(vals)

    def void(self, ids, context={}):
        obj = self.browse(ids[0])
        settings = get_model("settings").browse(1)
        if settings.lock_date:
            if obj.date < settings.lock_date:
                raise Exception("Accounting transaction is before lock date")
        obj.lines.unreconcile()
        obj.write({"state": "voided"})
        obj.delete_track_entries()
        get_model("field.cache").clear_cache(model="account.account")
        get_model("account.balance").update_balances()

    def delete_track_entries(self, ids, context={}):
        obj = self.browse(ids[0])
        obj.track_entries.delete()

    def get_total(self, ids, context):
        vals = {}
        for obj in self.browse(ids):
            total_debit = 0
            total_credit = 0
            for line in obj.lines:
                total_debit += line.debit
                total_credit += line.credit
            vals[obj.id] = {
                "total_debit": total_debit,
                "total_credit": total_credit,
            }
        return vals

    def update_amounts(self, context):
        data = context["data"]
        data["total_debit"] = 0
        data["total_credit"] = 0
        for line in data["lines"]:
            if not line:
                continue
            debit = line.get("debit") or 0
            credit = line.get("credit") or 0
            data["total_debit"] += debit
            data["total_credit"] += credit
            if line.get("debit") is not None and line.get("credit") is None:
                line["credit"] = 0
            if line.get("credit") is not None and line.get("debit") is None:
                line["debit"] = 0
        data["difference"] = data["total_debit"] - data["total_credit"]
        return data

    def get_line_desc(self, context):
        data = context["data"]
        path = context["path"]
        if not data.get("default_line_desc"):
            return
        if not get_data_path(data, path):
            set_data_path(data, path, data.get("narration"))
        return data

    def view_journal(self, ids, context={}):
        res = self.read(ids, ["related_id"])[0]["related_id"]
        rel = res and res[0] or None
        next = None
        if rel:
            model, model_id = rel.split(",")
            if model == "account.invoice":
                next = {
                    "name": "view_invoice",
                    "active_id": model_id,
                }
            elif model == "account.payment":
                next = {
                    "name": "payment",
                    "mode": "form",
                    "active_id": model_id,
                }
            elif model == "account.transfer":
                next = {
                    "name": "bank_transfer",
                    "mode": "form",
                    "active_id": model_id,
                }
            elif model == "account.claim":
                next = {
                    "name": "account_claim_edit",
                    "active_id": model_id,
                }
        if not next:
            next = {
                "name": "journal_entry",
                "mode": "form",
                "active_id": ids[0],
            }
        return {"next": next}

    def copy(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {
            "journal_id": obj.journal_id.id,
            "ref": obj.ref,
            "default_line_desc": obj.default_line_desc,
            "narration": obj.narration,
            "lines": [],
        }
        for line in obj.lines:
            line_vals = {
                "description": line.description,
                "account_id": line.account_id.id,
                "debit": line.debit,
                "credit": line.credit,
                "tax_comp_id": line.tax_comp_id.id,
                "tax_base": line.tax_base,
                "contact_id": line.contact_id.id,
                "track_id": line.track_id.id,
                "track2_id": line.track2_id.id,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals, context={"journal_id": obj.journal_id.id})
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "journal_entry",
                "mode": "form",
                "active_id": new_id,
            },
            "flash":
            "Journal entry %s copied to %s" % (obj.number, new_obj.number),
        }

    def to_draft(self, ids, context={}):
        obj = self.browse(ids)[0]
        for line in obj.lines:
            line.unreconcile()
        obj.write({"state": "draft"})
        obj.delete_track_entries()
        get_model("account.balance").update_balances()
        return {
            "next": {
                "name": "journal_entry",
                "mode": "form",
                "active_id": obj.id,
            },
            "flash": "Journal entry #%d set to draft" % obj.id,
        }

    def onchange_journal(self, context={}):
        data = context["data"]
        journal_id = data["journal_id"]
        date = data["date"]
        number = self._get_number(context={
            "journal_id": journal_id,
            "date": date
        })
        data["number"] = number
        return data

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

    def get_data(self, ids, context={}):
        company_id = get_active_company()
        comp = get_model("company").browse(company_id)
        settings = get_model('settings').browse(1)
        pages = []
        for obj in self.browse(ids):
            lines = []
            for line in obj.lines:
                lines.append({
                    'description': line.description,
                    'account_code': line.account_id.code,
                    'account_name': line.account_id.name,
                    'debit': line.debit,
                    'credit': line.credit,
                    'tax_comp': line.tax_comp_id.name,
                    'tax_base': line.tax_base,
                    'track': line.track_id.name,
                    'contact': line.contact_id.name,
                })
            data = {
                "comp_name": comp.name,
                "number": obj.number,
                "date": obj.date,
                "journal": obj.journal_id.name,
                "narration": obj.narration,
                "lines": lines,
                "total_debit": obj.total_debit,
                "total_credit": obj.total_credit,
            }
            if settings.logo:
                data['logo'] = get_file_path(settings.logo)
            pages.append(data)
        if pages:
            pages[-1]["is_last_page"] = True
        return {
            "pages": pages,
            "logo":
            get_file_path(settings.logo),  # XXX: remove when render_odt fixed
        }

    def reverse(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {
            "journal_id": obj.journal_id.id,
            "ref": obj.ref,
            "default_line_desc": obj.default_line_desc,
            "narration": obj.narration,
            "lines": [],
        }
        for line in obj.lines:
            line_vals = {
                "description": line.description,
                "account_id": line.account_id.id,
                "debit": line.credit,
                "credit": line.debit,
                "tax_comp_id": line.tax_comp_id.id,
                "tax_base": line.tax_base,
                "contact_id": line.contact_id.id,
                "track_id": line.track_id.id,
                "track2_id": line.track2_id.id,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals, context={"journal_id": obj.journal_id.id})
        self.post([new_id])
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "journal_entry",
                "mode": "form",
                "active_id": new_id,
            },
            "flash":
            "Journal entry %s reversed to %s" % (obj.number, new_obj.number),
            "reverse_move_id":
            new_id,
        }
Пример #25
0
class BarcodeReceive(Model):
    _name = "barcode.receive"
    _transient = True
    _fields = {
        "location_to_id":
        fields.Many2One("stock.location",
                        "To Location",
                        condition=[["type", "=", "internal"]]),
        "location_from_id":
        fields.Many2One("stock.location",
                        "From Location",
                        condition=[["type", "!=", "internal"]]),
        "journal_id":
        fields.Many2One("stock.journal", "Stock Journal"),
        "related_id":
        fields.Reference([["purchase.order", "Purchase Order"],
                          ["sale.order", "Sales Order"]], "Related To"),
        "lines":
        fields.One2Many("barcode.receive.line", "wizard_id", "Lines"),
        "state":
        fields.Selection([["pending", "Pending"], ["done", "Completed"]],
                         "Status",
                         required=True),
    }

    _defaults = {
        "state": "done",
    }

    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["uom_id"] = prod.uom_id.id
        return data

    def onchange_related(self, context={}):
        data = context["data"]
        val = data["related_id"][0]
        relation, rel_id = val.split(",")
        rel_id = int(rel_id)
        if relation == "purchase.order":
            res = get_model("stock.location").search(
                [["type", "=", "supplier"]])
            if not res:
                raise Exception("Supplier location not found")
            data["location_from_id"] = res[0]
        elif relation == "sale.order":
            res = get_model("stock.location").search(
                [["type", "=", "customer"]])
            if not res:
                raise Exception("Customer location not found")
            data["location_from_id"] = res[0]
        return data

    def fill_products(self, ids, context={}):
        obj = self.browse(ids)[0]
        rel = obj.related_id
        if not rel:
            raise Exception("Related not selected")
        order = obj.production_id
        found = False
        if rel._model == "purchase.order":
            for line in rel.lines:
                qty_remain = line.qty - line.qty_received
                if qty_remain <= 0:
                    continue
                vals = {
                    "wizard_id": obj.id,
                    "product_id": line.product_id.id,
                    "qty": qty_remain,
                    "uom_id": line.uom_id.id,
                }
                get_model("barcode.receive.line").create(vals)
                found = True
        elif rel._model == "sale.order":
            for line in rel.lines:
                if line.qty_delivered <= 0:
                    continue
                vals = {
                    "wizard_id": obj.id,
                    "product_id": line.product_id.id,
                    "qty": line.qty_delivered,
                    "uom_id": line.uom_id.id,
                }
                get_model("barcode.receive.line").create(vals)
                found = True
        if not found:
            raise Exception("No products remaining to receive")

    def clear(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {
            "location_from_id": None,
            "related_id": None,
            "lines": [("delete_all", )],
        }
        obj.write(vals)

    def validate(self, ids, context={}):
        obj = self.browse(ids)[0]
        if not obj.lines:
            raise Exception("Product list is empty")
        pick_vals = {
            "type": "in",
            "journal_id": obj.journal_id.id,
            "lines": [],
        }
        rel = obj.related_id
        if rel:
            pick_vals["related_id"] = "%s,%d" % (rel._model, rel.id)
        for line in obj.lines:
            line_vals = {
                "product_id": line.product_id.id,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "lot_id": line.lot_id.id,
                "location_from_id": obj.location_from_id.id,
                "location_to_id": obj.location_to_id.id,
                "container_to_id": line.container_to_id.id,
            }
            pick_vals["lines"].append(("create", line_vals))
        pick_id = get_model("stock.picking").create(
            pick_vals, context={"pick_type": "in"})
        if obj.state == "done":
            get_model("stock.picking").set_done([pick_id])
        elif obj.state == "pending":
            get_model("stock.picking").pending([pick_id])
        pick = get_model("stock.picking").browse(pick_id)
        obj.clear()
        return {
            "flash": "Goods receipt %s created successfully" % pick.number,
            "focus_field": "related_id",
        }
Пример #26
0
class Barcode(Model):
    _name = "stock.barcode"
    _transient = True
    _fields = {
        "station_id":
        fields.Many2One("barcode.station", "Barcode Station", required=True),
        "type":
        fields.Selection(
            [["in", "Goods Receipt"], ["internal", "Goods Transfer"],
             ["out", "Goods Issue"]], "Transaction Type"),
        "related_id":
        fields.Reference([["sale.order", "Sales Order"],
                          ["purchase.order", "Purchase Order"],
                          ["stock.picking", "Picking"]], "Related To"),
        "location_to_id":
        fields.Many2One("stock.location", "To Location"),
        "location_from_id":
        fields.Many2One("stock.location", "From Location"),
        "container_from_id":
        fields.Many2One("stock.container", "From Container"),
        "container_to_id":
        fields.Many2One("stock.container", "To Container"),
        "lot_id":
        fields.Many2One("stock.lot", "Lot / Serial Number"),
        "product_id":
        fields.Many2One("product", "Product"),
        "qty":
        fields.Decimal("Qty", scale=6),
        "uom_id":
        fields.Many2One("uom", "UoM"),
        "lines":
        fields.One2Many("stock.barcode.item", "barcode_id", "Lines"),
        "gross_weight":
        fields.Decimal("Gross Weight"),
    }

    def onchange_type(self, context={}):
        data = context["data"]
        station_id = data["station_id"]
        station = get_model("barcode.station").browse(station_id)
        type = data["type"]
        if type in ("out", "internal"):
            data["location_from_id"] = station.location_id.id
        elif type == "in":
            data["location_to_id"] = station.location_id.id
        return data

    def onchange_related(self, context={}):
        data = context["data"]
        """ Other module can use
        """
        return data

    def onchange_container_from(self, context={}):
        data = context["data"]
        cont_id = data["container_from_id"]
        data["container_to_id"] = cont_id
        return data

    def onchange_product(self, context={}):
        data = context["data"]
        prod_id = data["product_id"]
        prod = get_model("product").browse(prod_id)
        data["uom_id"] = prod.uom_id.id
        return data

    def add_container_products(self, ids, context={}):
        obj = self.browse(ids)[0]
        cont = obj.container_from_id
        if not cont:
            raise Exception("Missing container")
        contents = cont.get_contents()
        for (prod_id, lot_id, loc_id), (qty, amt, qty2) in contents.items():
            prod = get_model("product").browse(prod_id)
            if loc_id != obj.location_from_id.id:
                loc = get_model("stock.location").browse(loc_id)
                raise Exception("Invalid product location: %s @ %s" %
                                (prod.code, loc.name))
            vals = {
                "barcode_id": obj.id,
                "product_id": prod_id,
                "qty": qty,
                "uom_id": prod.uom_id.id,
                "qty2": qty2,
                "lot_id": lot_id,
            }
            get_model("stock.barcode.item").create(vals)
        return {
            "focus_field": "gross_weight",
        }

    def add_product(self, ids, context={}):
        print("barcode.add_product", ids)
        obj = self.browse(ids)[0]
        vals = {
            "barcode_id": obj.id,
            "product_id": obj.product_id.id,
            "qty": obj.qty,
            "uom_id": obj.uom_id.id,
            "lot_id": obj.lot_id.id,
        }
        get_model("stock.barcode.item").create(vals)
        obj.write({
            "lot_id": None,
            "product_id": None,
            "qty": None,
            "uom_id": None,
        })
        return {
            "focus_field": "product_id",
        }

    def validate(self, ids, context={}):
        obj = self.browse(ids)[0]
        rel = obj.related_id
        if rel and rel._model == "stock.picking":
            vals = {
                "picking_id": rel.id,
                "lines": [],
            }
            for line in obj.lines:
                line_vals = {
                    "product_id": line.product_id.id,
                    "qty": line.qty,
                    "uom_id": line.uom_id.id,
                }
                vals["lines"].append(("create", line_vals))
            val_id = get_model("pick.validate").create(vals)
            res = get_model("pick.validate").do_validate([val_id])
            return {
                "flash": res["flash"],
            }
        else:
            pick_vals = {
                "type": obj.type,
                "lines": [],
                "gross_weight": obj.gross_weight,
            }
            if rel:
                pick_vals["related_id"] = "%s,%d" % (rel._model, rel.id)
            for line in obj.lines:
                line_vals = {
                    "product_id": line.product_id.id,
                    "qty": line.qty,
                    "uom_id": line.uom_id.id,
                    "lot_id": line.lot_id.id,
                    "location_from_id": obj.location_from_id.id,
                    "location_to_id": obj.location_to_id.id,
                    "container_from_id": obj.container_from_id.id,
                    "container_to_id": obj.container_to_id.id,
                }
                pick_vals["lines"].append(("create", line_vals))
            pick_id = get_model("stock.picking").create(
                pick_vals, context={"pick_type": obj.type})
            get_model("stock.picking").set_done([pick_id])
            pick = get_model("stock.picking").browse(pick_id)
            obj.write({
                "type": None,
                "related_id": None,
                "location_from_id": None,
                "location_to_id": None,
                "container_from_id": None,
                "container_to_id": None,
                "product_id": None,
                "qty": None,
                "uom_id": None,
                "lot_id": None,
                "lines": [("delete_all", )],
            })
            return {
                "flash": "Stock picking %s created successfully" % pick.number,
                "focus_field": "type",
            }
Пример #27
0
class BarcodeIssue(Model):
    _name = "barcode.issue"
    _transient = True
    _fields = {
        "location_from_id": fields.Many2One("stock.location", "From Location", condition=[["type", "=", "internal"]]),
        "location_to_id": fields.Many2One("stock.location", "To Location", condition=[["type", "!=", "internal"]]),
        "journal_id": fields.Many2One("stock.journal", "Stock Journal"),
        "related_id": fields.Reference([["sale.order", "Sales Order"], ["purchase.order", "Purchase Order"]], "Related To"),
        "lines": fields.One2Many("barcode.issue.line", "wizard_id", "Lines"),
        "state": fields.Selection([["pending", "Pending"], ["done", "Completed"]], "Status", required=True),
        "container_from_id": fields.Many2One("stock.container", "From Container"),
    }

    _defaults = {
        "state": "done",
    }

    def onchange_related(self, context={}):
        data = context["data"]
        val = data["related_id"][0]
        relation, rel_id = val.split(",")
        rel_id = int(rel_id)
        if relation == "sale.order":
            res = get_model("stock.location").search([["type", "=", "customer"]])
            if not res:
                raise Exception("Customer location not found")
            data["location_to_id"] = res[0]
        elif relation == "purchase.order":
            res = get_model("stock.location").search([["type", "=", "supplier"]])
            if not res:
                raise Exception("Supplier location not found")
            data["location_to_id"] = res[0]
        return data

    def fill_products(self, ids, context={}):
        obj = self.browse(ids)[0]
        if obj.container_from_id:
            contents = obj.container_from_id.get_contents()
            for (prod_id, lot_id, loc_id), (qty, amt, qty2) in contents.items():
                if loc_id != obj.location_from_id.id:
                    continue
                prod = get_model("product").browse(prod_id)
                vals = {
                    "wizard_id": obj.id,
                    "product_id": prod_id,
                    "lot_id": lot_id,
                    "qty": qty,
                    "uom_id": prod.uom_id.id,
                    "qty2": qty2,
                    "container_from_id": obj.container_from_id.id,
                }
                get_model("barcode.issue.line").create(vals)
            obj.write({"container_from_id": None})
        else:
            rel = obj.related_id
            if rel._model == "sale.order":
                for line in rel.lines:
                    qty_remain = line.qty - line.qty_delivered
                    if qty_remain <= 0:
                        continue
                    vals = {
                        "wizard_id": obj.id,
                        "product_id": line.product_id.id,
                        "qty": qty_remain,
                        "uom_id": line.uom_id.id,
                    }
                    get_model("barcode.issue.line").create(vals)
            elif rel._model == "purchase.order":
                for line in rel.lines:
                    if line.qty_received <= 0:
                        continue
                    vals = {
                        "wizard_id": obj.id,
                        "product_id": line.product_id.id,
                        "qty": line.qty_received,
                        "uom_id": line.uom_id.id,
                    }
                    get_model("barcode.issue.line").create(vals)

    def clear(self, ids, context={}):
        obj = self.browse(ids)[0]
        vals = {
            "location_to_id": None,
            "related_id": None,
            "lines": [("delete_all",)],
        }
        obj.write(vals)

    def validate(self, ids, context={}):
        obj = self.browse(ids)[0]
        if not obj.lines:
            raise Exception("Product list is empty")
        pick_vals = {
            "type": "out",
            "journal_id": obj.journal_id.id,
            "lines": [],
        }
        rel = obj.related_id
        if rel:
            pick_vals["related_id"] = "%s,%d" % (rel._model, rel.id)
        for line in obj.lines:
            line_vals = {
                "product_id": line.product_id.id,
                "qty": line.qty,
                "uom_id": line.uom_id.id,
                "lot_id": line.lot_id.id,
                "qty2": line.qty2,
                "location_from_id": obj.location_from_id.id,
                "location_to_id": obj.location_to_id.id,
                "container_from_id": line.container_from_id.id,
                "container_to_id": line.container_to_id.id,
                "related_id": "%s,%s" % (line.related_id._model, line.related_id.id),
                "notes": line.notes,
            }
            pick_vals["lines"].append(("create", line_vals))
        pick_id = get_model("stock.picking").create(pick_vals, context={"pick_type": "out"})
        if obj.state == "done":
            get_model("stock.picking").set_done([pick_id])
        elif obj.state == "pending":
            get_model("stock.picking").pending([pick_id])
        pick = get_model("stock.picking").browse(pick_id)
        obj.clear()
        return {
            "next": {
                "name": "pick_out",
                "mode": "page",
                "active_id": pick.id,
            },
            "flash": "Goods issue %s created successfully" % pick.number,
            "focus_field": "related_id",
        }
Пример #28
0
class Invoice(Model):
    _name = "account.invoice"
    _string = "Invoice"
    _audit_log = True
    _key = ["company_id", "number"]
    _name_field = "number"
    _multi_company = True
    _fields = {
        "type": fields.Selection([["out", "Receivable"], ["in", "Payable"]], "Type", required=True),
        "inv_type": fields.Selection([["invoice", "Invoice"], ["credit", "Credit Note"], ["debit", "Debit Note"]], "Subtype", required=True, search=True),
        "number": fields.Char("Number", search=True),
        "ref": fields.Char("Ref", size=256, search=True),
        "memo": fields.Char("Memo", size=1024, search=True),
        "contact_id": fields.Many2One("contact", "Contact", required=True, search=True),
        "contact_credit": fields.Decimal("Outstanding Credit", function="get_contact_credit"),
        "account_id": fields.Many2One("account.account", "Account"),
        "date": fields.Date("Date", required=True, search=True),
        "due_date": fields.Date("Due Date", search=True),
        "currency_id": fields.Many2One("currency", "Currency", required=True, search=True),
        "tax_type": fields.Selection([["tax_ex", "Tax Exclusive"], ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]], "Tax Type", required=True),
        "state": fields.Selection([("draft", "Draft"), ("waiting_approval", "Waiting Approval"), ("waiting_payment", "Waiting Payment"), ("paid", "Paid"), ("voided", "Voided")], "Status", function="get_state", store=True, function_order=20, search=True),
        "lines": fields.One2Many("account.invoice.line", "invoice_id", "Lines"),
        "amount_subtotal": fields.Decimal("Subtotal", function="get_amount", function_multi=True, store=True),
        "amount_tax": fields.Decimal("Tax Amount", function="get_amount", function_multi=True, store=True),
        "amount_total": fields.Decimal("Total", function="get_amount", function_multi=True, store=True),
        "amount_paid": fields.Decimal("Paid Amount", function="get_amount", function_multi=True, store=True),
        "amount_due": fields.Decimal("Due Amount", function="get_amount", function_multi=True, store=True),
        "amount_credit_total": fields.Decimal("Total Credit", function="get_amount", function_multi=True, store=True),
        "amount_credit_remain": fields.Decimal("Remaining Credit", function="get_amount", function_multi=True, store=True),
        "amount_total_cur": fields.Decimal("Total Amount", function="get_amount", function_multi=True, store=True),
        "amount_due_cur": fields.Decimal("Due Amount", function="get_amount", function_multi=True, store=True),
        "amount_paid_cur": fields.Decimal("Paid Amount", function="get_amount", function_multi=True, store=True),
        "amount_credit_remain_cur": fields.Decimal("Remaining Credit", function="get_amount", function_multi=True, store=True),
        "amount_rounding": fields.Decimal("Rounding", function="get_amount", function_multi=True, store=True),
        "qty_total": fields.Decimal("Total Quantity", function="get_qty_total"),
        "attachment": fields.File("Attachment"),
        "payments": fields.One2Many("account.payment.line", "invoice_id", "Payments", condition=[["payment_id.state", "=", "posted"]]), # XXX: deprecated
        "move_id": fields.Many2One("account.move", "Journal Entry"),
        "reconcile_move_line_id": fields.Many2One("account.move.line", "Reconcile Item"), # XXX: deprecated
        "credit_alloc": fields.One2Many("account.credit.alloc", "credit_id", "Credit Allocation"), # XXX: deprecated
        "credit_notes": fields.One2Many("account.credit.alloc", "invoice_id", "Credit Notes"), # XXX: deprecated
        "currency_rate": fields.Decimal("Currency Rate", scale=6),
        "payment_id": fields.Many2One("account.payment", "Payment"),
        "related_id": fields.Reference([["sale.order", "Sales Order"], ["purchase.order", "Purchase Order"], ["production.order","Production Order"], ["project", "Project"], ["job", "Service Order"], ["service.contract", "Service Contract"]], "Related To"),
        "company_id": fields.Many2One("company", "Company"),
        "amount_discount": fields.Decimal("Discount", function="get_discount"),
        "bill_address_id": fields.Many2One("address", "Billing Address"),
        "comments": fields.One2Many("message", "related_id", "Comments"),
        "documents": fields.One2Many("document", "related_id", "Documents"),
        "fixed_assets": fields.One2Many("account.fixed.asset", "invoice_id", "Fixed Assets"),
        "tax_no": fields.Char("Tax No."),
        "tax_branch_no": fields.Char("Tax Branch No."),
        "pay_method_id": fields.Many2One("payment.method", "Payment Method"),
        "journal_id": fields.Many2One("account.journal", "Journal"),
        "sequence_id": fields.Many2One("sequence", "Sequence"),
        "original_invoice_id": fields.Many2One("account.invoice", "Original Invoice"),
        "product_id": fields.Many2One("product","Product",store=False,function_search="search_product",search=True),
        "taxes": fields.One2Many("account.invoice.tax","invoice_id","Taxes"),
        "agg_amount_total": fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]),
        "agg_amount_subtotal": fields.Decimal("Total Amount w/o Tax", agg_function=["sum", "amount_subtotal"]),
        "year": fields.Char("Year", sql_function=["year", "date"]),
        "quarter": fields.Char("Quarter", sql_function=["quarter", "date"]),
        "month": fields.Char("Month", sql_function=["month", "date"]),
        "week": fields.Char("Week", sql_function=["week", "date"]),
        "transaction_no": fields.Char("Transaction ID",search=True),
        "payment_entries": fields.One2Many("account.move.line",None,"Payment Entries",function="get_payment_entries"),
        "journal_date": fields.Date("Journal Date"),
    }
    _order = "date desc,number desc"

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    def get_payment_entries(self,ids,context={}):
        vals={}
        for obj in self.browse(ids):
            line_ids=[]
            if obj.move_id:
                inv_line=obj.move_id.lines[0]
                rec=inv_line.reconcile_id
                if rec:
                    for line in rec.lines:
                        if line.id!=inv_line.id:
                            line_ids.append(line.id)
            vals[obj.id]=line_ids
        return vals
Пример #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)
Пример #30
0
class SaleQuot(Model):
    _name = "sale.quot"
    _string = "Quotation"
    _audit_log = True
    _name_field = "number"
    _key = ["number", "company_id"]
    _multi_company = True
    _fields = {
        "number":
        fields.Char("Number", required=True, search=True),
        "ref":
        fields.Char("Ref", search=True),
        "contact_id":
        fields.Many2One("contact", "Contact", required=True, search=True),
        "date":
        fields.Date("Date", required=True, search=True),
        "exp_date":
        fields.Date("Valid Until"),
        "state":
        fields.Selection([("draft", "Draft"),
                          ("waiting_approval", "Awaiting Approval"),
                          ("approved", "Approved"), ("won", "Won"),
                          ("lost", "Lost"), ("revised", "Revised")],
                         "Status",
                         function="get_state",
                         store=True),
        "lines":
        fields.One2Many("sale.quot.line", "quot_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_words":
        fields.Char("Total Words", function="get_amount_total_words"),
        "qty_total":
        fields.Decimal("Total", function="get_qty_total"),
        "currency_id":
        fields.Many2One("currency", "Currency", required=True),
        "opport_id":
        fields.Many2One("sale.opportunity", "Opportunity", search=True),
        "user_id":
        fields.Many2One("base.user", "Owner", search=True),
        "tax_type":
        fields.Selection([["tax_ex", "Tax Exclusive"],
                          ["tax_in", "Tax Inclusive"], ["no_tax", "No Tax"]],
                         "Tax Type",
                         required=True),
        "sales":
        fields.One2Many("sale.order", "quot_id", "Sales Orders"),
        "payment_terms":
        fields.Text("Payment Terms"),
        "other_info":
        fields.Text("Other Information"),
        "comments":
        fields.One2Many("message", "related_id", "Comments"),
        "activities":
        fields.One2Many("activity", "related_id", "Activities"),
        "documents":
        fields.One2Many("document", "related_id", "Documents"),
        "uuid":
        fields.Char("UUID"),
        "price_list_id":
        fields.Many2One("price.list", "Price List"),
        "emails":
        fields.One2Many("email.message", "related_id", "Emails"),
        "company_id":
        fields.Many2One("company", "Company"),
        "related_id":
        fields.Reference([["issue", "Issue"]], "Related To"),
        "ship_term_id":
        fields.Many2One("ship.term", "Shipping Terms"),
        "sequence_id":
        fields.Many2One("sequence", "Number Sequence"),
        "job_template_id":
        fields.Many2One("job.template", "Service Order Template"),
        "lost_sale_code_id":
        fields.Many2One("reason.code",
                        "Lost Sale Reason Code",
                        condition=[["type", "=", "lost_sale"]]),
        "agg_amount_total":
        fields.Decimal("Total Amount", agg_function=["sum", "amount_total"]),
        "agg_amount_subtotal":
        fields.Decimal("Total Amount w/o Tax",
                       agg_function=["sum", "amount_subtotal"]),
        "year":
        fields.Char("Year", sql_function=["year", "date"]),
        "quarter":
        fields.Char("Quarter", sql_function=["quarter", "date"]),
        "month":
        fields.Char("Month", sql_function=["month", "date"]),
        "week":
        fields.Char("Week", sql_function=["week", "date"]),
        "est_costs":
        fields.One2Many("quot.cost", "quot_id", "Costs"),
        "est_cost_amount":
        fields.Float("Estimated Cost Amount",
                     function="get_est_profit",
                     function_multi=True),
        "est_profit_amount":
        fields.Float("Estimated Profit Amount",
                     function="get_est_profit",
                     function_multi=True),
        "est_margin_percent":
        fields.Float("Estimated Margin %",
                     function="get_est_profit",
                     function_multi=True),
        "est_cost_amount_conv":
        fields.Float("Estimated Cost Amount (Cur)",
                     function="get_est_profit",
                     function_multi=True),
        "est_profit_amount_conv":
        fields.Float("Estimated Profit Amount (Cur)",
                     function="get_est_profit",
                     function_multi=True),
        "est_margin_percent_conv":
        fields.Float("Estimated Margin % (Cur)",
                     function="get_est_profit",
                     function_multi=True),
        "est_sale_amount_conv":
        fields.Float("Sale Amount (Cur)",
                     function="get_est_profit",
                     function_multi=True),
        "est_sale_amount":
        fields.Float("Sale Amount",
                     function="get_est_profit",
                     function_multi=True),
        "currency_rates":
        fields.One2Many("custom.currency.rate", "related_id",
                        "Currency Rates"),
    }

    def _get_number(self, context={}):
        seq_id = get_model("sequence").find_sequence(type="sale_quot")
        if not seq_id:
            return None
        while 1:
            num = get_model("sequence").get_next_number(seq_id,
                                                        context=context)
            if not num:
                return None
            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")
        val = {
            "currency_id": settings.currency_id.id,
            "rate": settings.currency_id.get_rate(date, "sell") or 1
        }
        if context.get('action_name'):
            # default for new quotation create via quotation form
            lines.append(val)
        else:
            # When users create or copy quotation from other modules or methods, one2many field cannot be appended without action key
            # bacause it must be created in the database along with quotation itself.
            # If action key such as 'create', 'delete' is missing, the default line will not be created.
            # So, the action_key 'create' has to be appended into the list also.
            lines.append(("create", val))
        return lines

    _defaults = {
        "state": "draft",
        "date": lambda *a: time.strftime("%Y-%m-%d"),
        "number": _get_number,
        "currency_id": _get_currency,
        "tax_type": "tax_ex",
        "user_id": lambda self, context: get_active_user(),
        "uuid": lambda *a: str(uuid.uuid4()),
        "company_id": lambda *a: get_active_company(),
        "currency_rates": _get_currency_rates,
    }
    _constraints = ["check_fields"]
    _order = "date desc"

    def check_fields(self, ids, context={}):
        for obj in self.browse(ids):
            if context.get('is_draft'):
                continue
            dup = None
            if obj.state in ("waiting_approval", "approved"):
                if not obj.lines:
                    raise Exception("No lines in quotation")
            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 create(self, vals, **kw):
        id = super().create(vals, **kw)
        self.function_store([id])
        if 'lines' in vals.keys():
            self.create_est_costs([id])
        return id

    def write(self, ids, vals, **kw):
        opport_ids = []
        for obj in self.browse(ids):
            if obj.opport_id:
                opport_ids.append(obj.opport_id.id)
        super().write(ids, vals, **kw)
        if opport_ids:
            get_model("sale.opportunity").function_store(opport_ids)
        if 'lines' in vals.keys():
            self.create_est_costs(ids)
        ctx = kw.get('context', {})
        self.function_store(ids, context=ctx)

    def function_store(self, ids, field_names=None, context={}):
        super().function_store(ids, field_names, context)
        opport_ids = []
        for obj in self.browse(ids):
            if obj.opport_id:
                opport_ids.append(obj.opport_id.id)
        if opport_ids:
            get_model("sale.opportunity").function_store(opport_ids)

    def get_amount(self, ids, context={}):
        res = {}
        for obj in self.browse(ids):
            vals = {}
            subtotal = 0
            tax = 0
            for line in obj.lines:
                if line.is_hidden:
                    continue
                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 or 0) - line_tax
                else:
                    subtotal += line.amount or 0
            tax = get_model("currency").round(obj.currency_id.id, tax)
            vals["amount_subtotal"] = subtotal
            vals["amount_tax"] = tax
            vals["amount_total"] = subtotal + tax
            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 submit_for_approval(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state != "draft":
                raise Exception("Invalid state")
            obj.write({"state": "waiting_approval"})
        self.trigger(ids, "submit_for_approval")

    def approve(self, ids, context={}):
        for obj in self.browse(ids):
            if obj.state not in ("draft", "waiting_approval"):
                raise Exception("Invalid state")
            obj.write({"state": "approved"})

    def update_amounts(self, context):
        data = context["data"]
        data["amount_subtotal"] = 0
        data["amount_tax"] = 0
        tax_type = data["tax_type"]

        #===============>>>
        def _get_relative_currency_rate(currency_id):
            rate = None
            for r in data['currency_rates']:
                if r.get('currency_id') == currency_id:
                    rate = r.get('rate') or 0
                    break
            if rate is None:
                rate_from = get_model("currency").get_rate(
                    [currency_id], data['date']) or Decimal(1)
                rate_to = get_model("currency").get_rate(
                    [data['currency_id']], data['date']) or Decimal(1)
                rate = rate_from / rate_to
            return rate

        item_costs = {}
        for cost in data['est_costs']:
            if not cost:
                continue
            amt = cost['amount'] or 0
            if cost.get('currency_id'):
                print(cost.get("currency_id.id"), cost.get("currency_id"))
                rate = _get_relative_currency_rate(cost.get("currency_id"))
                amt = amt * rate
            comps = []
            if cost.get("sequence"):
                for comp in cost['sequence'].split("."):
                    comps.append(comp)
                    path = ".".join(comps)
                    k = (data['id'], path)
                    item_costs.setdefault(k, 0)
                    item_costs[k] += amt
        #<<<===============
        for line in data["lines"]:
            if not line:
                continue
            amt = (line.get("qty") or 0) * (line.get("unit_price") or 0)
            if line.get("discount"):
                disc = Decimal(amt) * Decimal(line["discount"]) / Decimal(100)
                amt -= disc
            if line.get("discount_amount"):
                amt -= line["discount_amount"]
            amt = Decimal(roundup(amt))
            line["amount"] = amt
            #===============>>>
            k = None
            if id in data:
                k = (data['id'], line.get("sequence", 0))
            else:
                k = (line.get("sequence", 0))
            cost = item_costs.get(k, 0)
            profit = amt - cost
            margin = profit * 100 / amt if amt else 0
            line["est_cost_amount"] = cost
            line["est_profit_amount"] = profit
            line["est_margin_percent"] = margin
            #<<<===============
        hide_parents = []
        for line in data["lines"]:
            if not line:
                continue
            if line.get("sequence") and line.get("hide_sub"):
                hide_parents.append(line["sequence"])
        is_hidden = {}
        hide_totals = {}
        for line in data["lines"]:
            if not line:
                continue
            if not line.get("sequence"):
                continue
            parent_seq = None
            for seq in hide_parents:
                if line["sequence"].startswith(seq + "."):
                    parent_seq = seq
                    break
            if parent_seq:
                is_hidden[line["sequence"]] = True
                hide_totals.setdefault(parent_seq, 0)
                hide_totals[parent_seq] += line["amount"]
        for line in data["lines"]:
            if not line:
                continue
            if line.get("sequence") and line.get("hide_sub"):
                line["amount"] = hide_totals.get(line["sequence"], 0)
                if line["qty"]:
                    line["unit_price"] = line["amount"] / line["qty"]
        for line in data["lines"]:
            if not line:
                continue
            if is_hidden.get(line.get("sequence")):
                continue
            tax_id = line.get("tax_id")
            if tax_id:
                tax = get_model("account.tax.rate").compute_tax(
                    tax_id, line["amount"], tax_type=tax_type)
                data["amount_tax"] += tax
            else:
                tax = 0
            if tax_type == "tax_in":
                data["amount_subtotal"] += Decimal(line["amount"] - tax)
            else:
                data["amount_subtotal"] += Decimal(line["amount"])
        data['amount_tax'] = get_model("currency").round(
            data['currency_id'], data['amount_tax'])
        data["amount_total"] = data["amount_subtotal"] + data["amount_tax"]
        return data

    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="sell") or 1
            print('currency_rate ', currency_rate)
        return currency_rate

    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["est_margin_percent_input"] = prod.gross_profit
        line["qty"] = prod.sale_unit_qty or prod.min_sale_qty or 1
        if prod.sale_uom_id or prod.uom_id is not None:
            line["uom_id"] = prod.sale_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)
            line["unit_price"] = price
            line['discount'] = get_model("price.list").get_discount(
                pricelist_id, prod.id, 1)
        if not price:
            price = prod.sale_price
            settings = get_model("settings").browse(1)
            currency_rate = self.get_currency_rate(context)
            currency_id = data["currency_id"]
            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
        if prod.sale_tax_id is not None:
            line["tax_id"] = prod.sale_tax_id.id
        if prod.categ_id and prod.categ_id.sale_tax_id:
            line["tax_id"] = prod.categ_id.sale_tax_id.id
        contact_id = data.get('contact_id')
        if contact_id:
            contact = get_model("contact").browse(contact_id)
            if contact.tax_receivable_id:
                line["tax_id"] = contact.tax_receivable_id.id
        if data.get("tax_type", "") == "no_tax":
            line["tax_id"] = None
        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.get("qty") or 0
        if line.get("unit_price") is None:
            if pricelist_id:
                line['unit_price'] = get_model("price.list").get_price(
                    pricelist_id, prod.id, qty)
            if not line.get('unit_price'):
                price = prod.sale_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,
                    currency_id,
                    settings.currency_id.id,
                    from_rate=1,
                    to_rate=currency_rate)
                line["unit_price"] = price_cur
        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="sell") or 1
        data = self.update_line_currency(context)
        return data

    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="sell") 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 update_line_currency(self, context):
        settings = get_model("settings").browse(1)
        data = context['data']
        currency_id = data["currency_id"]
        currency_rate = self.get_currency_rate(context)
        pricelist_id = data["price_list_id"]
        for line in data['lines']:
            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:
                line['unit_price'] = get_model("price.list").get_price(
                    pricelist_id, prod.id, qty)
                continue
            price = prod.sale_price or 0
            settings = get_model("settings").browse(1)
            currency_id = data["currency_id"]
            price_cur = get_model("currency").convert(price,
                                                      currency_id,
                                                      settings.currency_id.id,
                                                      from_rate=1,
                                                      to_rate=currency_rate)
            line["unit_price"] = price_cur
        data = self.update_amounts(context)
        return data

    def onchange_contact(self, context):
        data = context["data"]
        contact_id = data.get("contact_id")
        if not contact_id:
            return {}
        contact = get_model("contact").browse(contact_id)
        data["payment_terms"] = contact.payment_terms
        data["price_list_id"] = contact.sale_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 onchange_uom(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)
        uom_id = line.get("uom_id")
        if not uom_id:
            return {}
        uom = get_model("uom").browse(uom_id)
        if prod.sale_price is not None:
            line[
                "unit_price"] = prod.sale_price * uom.ratio / prod.uom_id.ratio
        data = self.update_amounts(context)
        return data

    def copy(self, ids, context):
        obj = self.browse(ids)[0]
        vals = {
            "ref": obj.number,
            "contact_id": obj.contact_id.id,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "payment_terms": obj.payment_terms,
            "other_info": obj.other_info,
            "exp_date": obj.exp_date,
            "opport_id": obj.opport_id.id,
            "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,
                "discount": line.discount,
                "discount_amount": line.discount_amount,
                "tax_id": line.tax_id.id,
                'amount': line.amount,
                'sequence': line.sequence,
            }
            vals["lines"].append(("create", line_vals))
        new_id = self.create(vals, context=context)
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "quot",
                "mode": "form",
                "active_id": new_id,
            },
            "flash":
            "Quotation %s copied from %s" % (new_obj.number, obj.number),
        }

    def revise(self, ids, context):
        obj = self.browse(ids)[0]
        res = self.copy(ids, context)
        obj.write({"state": "revised"})
        return res

    def copy_to_sale_order(self, ids, context):
        id = ids[0]
        obj = self.browse(id)
        sale_vals = {
            "quot_id": obj.id,
            "contact_id": obj.contact_id.id,
            "currency_id": obj.currency_id.id,
            "tax_type": obj.tax_type,
            "ref": obj.ref,
            "lines": [],
            "user_id": obj.user_id.id,
            "other_info": obj.other_info,
            "payment_terms": obj.payment_terms,
            "price_list_id": obj.price_list_id.id,
            "job_template_id": obj.job_template_id.id,
            "est_costs": [],
            "currency_rates": [],
            "related_id": "sale.quot,%s" % obj.id,
        }
        for line in obj.lines:
            if not line.qty:
                continue
            prod = line.product_id
            line_vals = {
                "sequence": line.sequence,
                "product_id": prod.id,
                "description": line.description,
                "qty": line.qty,
                "uom_id": line.uom_id and line.uom_id.id or None,
                "unit_price": line.unit_price if not line.is_hidden else 0,
                "discount": line.discount if not line.is_hidden else 0,
                "discount_amount":
                line.discount_amount if not line.is_hidden else 0,
                "tax_id": line.tax_id.id if not line.is_hidden else None,
                "location_id": prod.location_id.id if prod else None,
            }
            if prod.locations:
                line_vals["location_id"] = prod.locations[0].location_id.id
                for loc in prod.locations:
                    if loc.stock_qty:
                        line_vals['location_id'] = loc.location_id.id
                        break
            sale_vals["lines"].append(("create", line_vals))
        for r in obj.currency_rates:
            rate_vals = {
                "currency_id": r.currency_id.id,
                "rate": r.rate,
            }
            sale_vals["currency_rates"].append(("create", rate_vals))
        sale_id = get_model("sale.order").create(sale_vals, context=context)
        sale = get_model("sale.order").browse(sale_id)
        return {
            "next": {
                "name": "sale",
                "mode": "form",
                "active_id": sale_id,
            },
            "flash":
            "Sale order %s created from quotation %s" %
            (sale.number, obj.number)
        }

    def do_won(self, ids, context={}):
        for obj in self.browse(ids):
            assert obj.state == "approved"
            obj.write({"state": "won"})

    def do_lost(self, ids, context={}):
        for obj in self.browse(ids):
            assert obj.state == "approved"
            obj.write({"state": "lost"})

    def do_reopen(self, ids, context={}):
        for obj in self.browse(ids):
            assert obj.state in ("won", "lost")
            obj.write({"state": "approved"})

    def get_state(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            state = obj.state
            if state == "approved":
                found = False
                for sale in obj.sales:
                    if sale.state in ("confirmed", "done"):
                        found = True
                        break
                if found:
                    state = "won"
            vals[obj.id] = state
        return vals

    def view_link(self, ids, context={}):
        obj = self.browse(ids)[0]
        uuid = obj.uuid
        dbname = get_active_db()
        return {
            "next": {
                "type": "url",
                "url": "/view_quot?dbname=%s&uuid=%s" % (dbname, uuid),
            }
        }

    def get_template_quot_form(self, ids, context={}):
        obj = self.browse(ids)[0]
        has_discount = False
        for line in obj.lines:
            if line.discount:
                has_discount = True
        if has_discount:
            return "quot_form_disc"
        else:
            return "quot_form"

    def to_draft(self, ids, context={}):
        obj = self.browse(ids)[0]
        context['is_draft'] = True
        obj.write({"state": "draft"}, context=context)

    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"]
        context['date'] = data['date']
        if not seq_id:
            seq_id = get_model("sequence").find_sequence(type="sale_quot")
        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 onchange_cost_product(self, context):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        prod_id = line.get("product_id")
        if prod_id:
            prod = get_model("product").browse(prod_id)
            line["description"] = prod.name
            line["list_price"] = prod.purchase_price
            line["purchase_price"] = prod.purchase_price
            line["landed_cost"] = prod.landed_cost
            line["qty"] = 1
            line["uom_id"] = prod.uom_id.id
            line["currency_id"] = prod.purchase_currency_id.id
            line["purchase_duty_percent"] = prod.purchase_duty_percent
            line["purchase_ship_percent"] = prod.purchase_ship_percent
            line["landed_cost"] = prod.landed_cost
            line["amount"] = line['qty'] * line['landed_cost'] or 0
            if prod.suppliers:
                line["supplier_id"] = prod.suppliers[0].supplier_id.id
        return data

    def get_est_profit(self, ids, context={}):
        vals = {}
        for obj in self.browse(ids):
            cost = 0
            cost_conv = 0
            settings = get_model('settings').browse(1)
            currency_id = settings.currency_id.id
            cur_rate = self.get_relative_currency_rate(ids, obj.currency_id.id)
            amount = obj.amount_subtotal * cur_rate
            amount_conv = obj.amount_subtotal
            if currency_id == obj.currency_id.id:
                amount_conv = 0
            for line_cost in obj.est_costs:
                cost += line_cost.amount or 0
                cost_conv += line_cost.amount_conv or 0
            profit = (amount or 0) - cost
            margin = (profit / amount) * 100 if amount else None
            profit_conv = (amount_conv or 0) - cost_conv
            margin_conv = (profit_conv /
                           amount_conv) * 100 if amount_conv else None
            vals[obj.id] = {
                "est_cost_amount": cost,
                "est_profit_amount": profit,
                "est_margin_percent": margin,
                "est_cost_amount_conv": cost_conv if amount_conv else 0,
                "est_profit_amount_conv": profit_conv if amount_conv else 0,
                "est_margin_percent_conv": margin_conv if amount_conv else 0,
                "est_sale_amount_conv": amount_conv,
                "est_sale_amount": amount,
            }
        return vals

    def create_est_costs(self, ids, context={}):
        obj = self.browse(ids[0])
        del_ids = []
        for cost in obj.est_costs:
            if cost.product_id:
                del_ids.append(cost.id)
        get_model("quot.cost").delete(del_ids)
        #obj.write({"est_costs":[("delete_all",)]})
        line_sequence = 1
        settings = get_model("settings").browse(1)
        for line in obj.lines:
            prod = line.product_id
            cur_line_sequence = line_sequence
            landed_cost = prod.landed_cost
            if not prod:
                continue
            if not prod.purchase_price and prod.type != "service":
                continue
            if not prod.cost_price and prod.type == "service":
                continue
            if "bundle" == prod.type:
                continue
            # update line seqence
            if not line.sequence:
                line.write({"sequence": cur_line_sequence})
                line_sequence += 1
            else:
                line_sequence = round(Decimal(line.sequence)) + Decimal(1)
            # compute cost if product is service
            if prod.type == "service":
                if prod.uom_id.type == 'time':  #day
                    landed_cost = prod.cost_price or 0
                elif prod.uom_id.type == 'unit':  #job
                    landed_cost = (prod.sale_price or 0) * (prod.cost_price
                                                            or 0)  #percentage
            #elif prod.type == "stock" and prod.supply_method == 'production':
            #landed_cost = prod.cost_price or 0
            # update landed cost
            per_duty = ((Decimal('1.0') * prod.purchase_duty_percent
                         if prod.purchase_duty_percent else Decimal('0')) /
                        Decimal('100.0'))
            per_ship = ((Decimal('1.0') * prod.purchase_ship_percent
                         if prod.purchase_ship_percent else Decimal('0')) /
                        Decimal('100.0'))
            landed_cost = prod.purchase_price + (
                prod.purchase_price * per_ship) + (prod.purchase_price *
                                                   per_duty)
            vals = {
                "quot_id":
                obj.id,
                "sequence":
                (line.sequence if not line.is_hidden else line.parent_sequence)
                if line.sequence else cur_line_sequence,
                "product_id":
                prod.id,
                "description":
                prod.name,
                "supplier_id":
                prod.suppliers[0].supplier_id.id if prod.suppliers else None,
                "list_price":
                prod.purchase_price,
                "purchase_price":
                prod.purchase_price,
                #"landed_cost": prod.cost_price if prod.type == "service" else prod.landed_cost,
                "landed_cost":
                landed_cost,
                "purchase_duty_percent":
                prod.purchase_duty_percent,
                "purchase_ship_percent":
                prod.purchase_ship_percent,
                "qty":
                line.qty,
                "currency_id":
                prod.purchase_currency_id.id or settings.currency_id.id,
            }
            get_model("quot.cost").create(vals)

    def merge_quotations(self, ids, context={}):
        if len(ids) < 2:
            raise Exception("Can not merge less than two quotations")
        cur_rate = self.get_relative_currency_rate(ids, obj.currency_id.id)
        contact_ids = []
        currency_ids = []
        tax_types = []
        for obj in self.browse(ids):
            contact_ids.append(obj.contact_id.id)
            currency_ids.append(obj.currency_id.id)
            tax_types.append(obj.tax_type)
        contact_ids = list(set(contact_ids))
        currency_ids = list(set(currency_ids))
        tax_types = list(set(tax_types))
        if len(contact_ids) > 1:
            raise Exception("Quotation customers have to be the same")
        if len(currency_ids) > 1:
            raise Exception("Quotation currencies have to be the same")
        if len(tax_types) > 1:
            raise Exception("Quotation tax types have to be the same")
        vals = {
            "contact_id": contact_ids[0],
            "currency_id": currency_ids[0],
            "tax_type": tax_types[0],
            "lines": [],
            "est_costs": [],
        }
        seq = 0
        refs = []
        for obj in sorted(self.browse(ids), key=lambda obj: obj.number):
            refs.append(obj.number)
            seq_map = {}
            for line in obj.lines:
                seq += 1
                seq_map[line.sequence] = seq
                qty = line.qty or 0
                unit_price = line.unit_price or 0
                amt = qty * unit_price
                disc = amt * (line.discount or 0) / Decimal(100)
                line_vals = {
                    "sequence": seq,
                    "product_id": line.product_id.id,
                    "description": line.description,
                    "qty": qty,
                    "uom_id": line.uom_id.id,
                    "unit_price": unit_price,
                    "discount": disc,
                    "amount": amt,
                    "tax_id": line.tax_id.id,
                }
                vals["lines"].append(("create", line_vals))
            for cost in obj.est_costs:
                cost_vals = {
                    "sequence": seq_map.get(cost.sequence),
                    "product_id": cost.product_id.id,
                    "description": cost.description,
                    "supplier_id": cost.supplier_id.id,
                    "list_price": cost.list_price,
                    "purchase_price": cost.purchase_price,
                    "landed_cost": cost.landed_cost,
                    "qty": cost.qty,
                    "currency_id": cost.currency_id.id,
                }
                vals["est_costs"].append(("create", cost_vals))
        vals['ref'] = ', '.join([ref for ref in refs])
        new_id = self.create(vals, context=context)
        new_obj = self.browse(new_id)
        return {
            "next": {
                "name": "quot",
                "mode": "form",
                "active_id": new_id,
            },
            "flash": "Quotations merged",
        }

    def onchange_est_margin(self, context={}):
        data = context["data"]
        path = context["path"]
        line = get_data_path(data, path, parent=True)
        margin = line["est_margin_percent_input"]
        amt = line["est_cost_amount"] / (1 - margin / Decimal(100))
        price = round(amt / line["qty"])
        line["unit_price"] = price
        self.update_amounts(context)
        return data

    def get_relative_currency_rate(self, ids, currency_id):
        obj = self.browse(ids[0])
        rate = None
        for r in obj.currency_rates:
            if r.currency_id.id == currency_id:
                rate = r.rate
                break
        if rate is None:
            rate_from = get_model("currency").get_rate([currency_id],
                                                       obj.date) or Decimal(1)
            rate_to = obj.currency_id.get_rate(obj.date) or Decimal(1)
            rate = rate_from / rate_to
        return rate

    def update_cost_amount(self, context={}):
        settings = get_model('settings').browse(1)
        data = context['data']
        path = context['path']
        line = get_data_path(data, path, parent=True)

        pur_price = round(Decimal(line['purchase_price'] or 0), 2)
        purchase_ship_percent = round(
            Decimal(line['purchase_ship_percent'] or 0), 2)
        purchase_duty_percent = round(
            Decimal(line['purchase_duty_percent'] or 0), 2)
        qty = round(Decimal(line['qty'] or 0), 2)
        amount = Decimal(0)

        landed_cost = round(
            pur_price + (pur_price * (purchase_ship_percent / 100)) +
            (pur_price * (purchase_duty_percent / 100)), 2)
        amount = round((qty) * (landed_cost), 2)

        line['landed_cost'] = landed_cost
        prod = get_model('product').browse(line.get('product_id'))
        if prod.purchase_currency_id:
            if prod.purchase_currency_id.id == settings.currency_id.id:
                if data["currency_id"] == settings.currency_id.id:
                    line['amount'] = amount
                    line['amount_conv'] = amount
                else:
                    cost_conv = get_model("currency").convert(
                        amount,
                        settings.currency_id.id,
                        data['currency_id'],
                        date=data['date'])
                    line['amount'] = amount
                    line['amount_conv'] = cost_conv
            else:
                cost = get_model("currency").convert(
                    amount,
                    prod.purchase_currency_id.id,
                    settings.currency_id.id,
                    date=data['date'])
                cost_conv = get_model("currency").convert(
                    cost,
                    settings.currency_id.id,
                    data['currency_id'],
                    date=data['date'])
                line['amount'] = cost
                line['amount_conv'] = cost_conv
        else:
            if data["currency_id"] != settings.currency_id.id:
                cost = get_model("currency").convert(amount,
                                                     data['currency_id'],
                                                     settings.currency_id.id,
                                                     date=data['date'])
                cost_conv = get_model("currency").convert(
                    cost,
                    settings.currency_id.id,
                    data['currency_id'],
                    date=data['date'])
                line['amount'] = cost
                line['amount_conv'] = cost_conv
            else:
                line['amount'] = amount
                line['amount_conv'] = amount
        return data

    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']:
                if 'product_id' not in line:
                    continue
                product_id = line['product_id']
                if not product_id:
                    continue
                if 'tax_id' not in line or line['tax_id']:
                    continue
                product = get_model("product").browse(product_id)
                if product.sale_tax_id:
                    line["tax_id"] = product.sale_tax_id.id
        data = self.update_amounts(context)
        return data

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