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"
class RequiredDoc(Model): _name = "required.doc" _string = "Required Document" _fields = { "name": fields.Text("Document Name", required=True), "related_id": fields.Reference([], "Related To"), }
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"), }
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
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"), }
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")
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), }
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"), }
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
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")
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")), }
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)
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))
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
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
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
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
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
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
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)
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, }
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)
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, }
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", }
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", }
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", }
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
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)
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