class Website(Model): _name = "website" _string = "Website" _fields = { "name": fields.Char("Website Title"), "parent_categ_id": fields.Many2One("product.categ", "Product Category"), "parent_group_id": fields.Many2One("product.group", "Product Group"), "contact_categ_id": fields.Many2One("contact.categ", "Customer Contact Category"), "user_profile_id": fields.Many2One("profile", "Customer User Profile"), "sale_account_id": fields.Many2One("account.account", "Sales Account"), "sale_tax_id": fields.Many2One("account.tax.rate", "Sales Tax"), "account_receivable_id": fields.Many2One("account.account", "Receivable Account"), "news_categ_id": fields.Many2One("contact.categ", "Newsletter Contact Category"), "target_list_id": fields.Many2One("mkt.target.list", "Newsletter Target List"), "invoice_flag": fields.Boolean("Use same invoice number as sale order number"), "ship_product_id": fields.Many2One("product", "Shipping Product"), "preview_doc_categ_id": fields.Many2One("document.categ", "Preview picture document category"), "invoice_template_id": fields.Many2One("report.template", "Invoice Template"), "payment_slip_template_id": fields.Many2One("report.template", "Payment Slip Template"), "auto_create_account": fields.Boolean("Auto-create customer account after checkout"), "ga_script": fields.Text("Google Analytic script"), "state": fields.Selection([["active", "Active"], ["inactive", "Inactive"]], "Status", required=True), "theme_id": fields.Many2One("theme", "Theme"), "settings": fields.One2Many("website.setting","website_id","Website Settings"), "sale_channel_id": fields.Many2One("sale.channel","Sales Channel"), "bank_method_id": fields.Many2One("payment.method","Bank Transfer",condition=[["type","=","bank"]]), "paypal_method_id": fields.Many2One("payment.method","Paypal",condition=[["type","=","paypal"]]), "paysbuy_method_id": fields.Many2One("payment.method","Paysbuy",condition=[["type","=","paysbuy"]]), "scb_method_id": fields.Many2One("payment.method","SCB Gateway",condition=[["type","=","scb_gateway"]]), "url": fields.Char("Website URL"), } _order="name" _defaults = { "state": "active", }
class ReportTaxSum(Model): _name = "report.tax.sum" _transient = True _fields = { "date_from": fields.Date("From"), "date_to": fields.Date("To"), "by_rate": fields.Boolean("Show by Tax Rate"), "by_comp": fields.Boolean("Show by Tax Component"), } _defaults = { "date_from": lambda *a: date.today().strftime("%Y-%m-01"), "date_to": lambda *a: (date.today() + relativedelta(day=31)).strftime("%Y-%m-%d"), "by_comp": True, } def get_report_data(self, ids, context={}): company_id = get_active_company() company_ids = get_model("company").search( [["id", "child_of", company_id]]) comp = get_model("company").browse(company_id) if ids: params = self.read(ids, load_m2o=False)[0] else: params = self.default_get(load_m2o=False, context=context) settings = get_model("settings").browse(1) date_from = params.get("date_from") if not date_from: date_from = date.today().strftime("%Y-%m-01") date_to = params.get("date_to") if not date_to: date_to = (date.today() + relativedelta(day=31)).strftime("%Y-%m-%d") data = { "company_name": comp.name, "date_from": date_from, "date_to": date_to, "by_rate": params.get("by_rate"), "by_comp": params.get("by_comp"), } db = database.get_connection() if params.get("by_comp"): res = db.query( "SELECT c.id AS comp_id,c.name AS comp_name,c.rate AS comp_rate,r.name AS rate_name,SUM(l.credit-l.debit) AS tax_total,SUM(l.tax_base*sign(l.credit-l.debit)) AS base_total FROM account_move_line l,account_move m,account_tax_component c,account_tax_rate r WHERE m.id=l.move_id AND m.state='posted' AND m.date>=%s AND m.date<=%s AND c.id=l.tax_comp_id AND r.id=c.tax_rate_id AND m.company_id IN %s GROUP BY comp_id,comp_name,comp_rate,rate_name ORDER BY comp_name,rate_name", date_from, date_to, tuple(company_ids)) data["comp_taxes"] = [dict(r) for r in res] if params.get("by_rate"): res = db.query( "SELECT c.id AS comp_id,c.name AS comp_name,c.rate AS comp_rate,r.name AS rate_name,SUM(l.credit-l.debit) AS tax_total,SUM(l.tax_base*sign(l.credit-l.debit)) AS base_total FROM account_move_line l,account_move m,account_tax_component c,account_tax_rate r WHERE m.id=l.move_id AND m.state='posted' AND m.date>=%s AND m.date<=%s AND c.id=l.tax_comp_id AND r.id=c.tax_rate_id AND m.company_id IN %s GROUP BY comp_id,comp_name,comp_rate,rate_name ORDER BY rate_name,comp_name", date_from, date_to, tuple(company_ids)) data["rate_taxes"] = [dict(r) for r in res] return data
class Profile(Model): _name = "profile" _string = "Profile" _key = ["name"] _fields = { "name": fields.Char("Name", required=True, search=True), "code": fields.Char("Short Code"), "perms": fields.One2Many("profile.access", "profile_id", "Model Permissions"), "field_perms": fields.One2Many("field.access", "profile_id", "Field Permissions"), "menu_perms": fields.One2Many("menu.access", "profile_id", "Menu Permissions"), "other_perms": fields.Many2Many("permission", "Other Permissions"), "home_action": fields.Char("Login Action"), "login_company_id": fields.Many2One("company", "Login Company"), "prevent_login": fields.Boolean("Prevent Login"), "comments": fields.One2Many("message", "related_id", "Comments"), "default_model_perms": fields.Selection([["full", "Full Access"], ["readonly","Read-only Access"], ["no", "No Access"]], "Default Model Permissions"), "default_menu_access": fields.Selection([["visible", "Visible"], ["hidden", "Hidden"]], "Default Menu Access"), } _order = "name" _defaults = { "default_model_perms": "full", } def get_data(self, context={}): vals = {} perms = [] for m in get_model("model").search_browse([]): perms.append({ "model_id": [m.id, m.string], }) vals["perms"] = perms return vals def copy(self, ids, context={}): obj = self.browse(ids)[0] vals = { "name": obj.name + " (Copy)", "perms": [], "other_perms": [("set", [p.id for p in obj.other_perms])], "home_action": obj.home_action, } for perm in obj.perms: vals["perms"].append(("create", { "model_id": perm.model_id.id, "perm_read": perm.perm_read, "perm_create": perm.perm_create, "perm_write": perm.perm_write, "perm_delete": perm.perm_delete, "view_all": perm.view_all, "modif_all": perm.modif_all, })) profile_id = get_model("profile").create(vals) return { "next": { "name": "profile", "mode": "form", "active_id": profile_id, }, "flash": "New profile created", }
class Product(Model): _inherit = "product" _fields = { "review": fields.One2Many("ecom.product.review", "product_id", "Product Review"), "wishlist": fields.One2Many("ecom.wishlist", "product_id", "Wishlist"), "has_sample": fields.Boolean("Has Sample", function="check_sample"), "avg_rate": fields.Integer("Average Rating", function="get_rating"), } def check_sample(self, ids, context={}): vals = {} for obj in self.browse(ids): product = obj sample = False for img in product.images: if img.title: if "SAMPLE_OPTS_" in img.title: sample = True vals[obj.id] = sample return vals def get_rating(self, ids, context={}): vals = {} for obj in self.browse(ids): score = [] for review in get_model("ecom.product.review").search_browse( [["product_id", "=", obj.id], ["state", "=", "approved"]]): score.append(int(review.rating or 0)) vals[obj.id] = sum(score) / len(score) if score else 0 return vals
class CustomOption(Model): _name = "product.custom.option" _string = "Custom Option" _key = ["code"] _fields = { "name": fields.Char("Name", required=True, search=True, translate=True), "seq": fields.Char("Sequence", required=True), "code": fields.Char("Code", search=True), "type": fields.Selection([["text", "Text"], ["selection", "Selection"]], "Type", required=True), "required": fields.Boolean("Required"), "description": fields.Text("Description"), "price": fields.Decimal("Price"), "values": fields.One2Many("product.custom.option.value", "cust_opt_id", "Values"), } _defaults = { "type": "text", "seq": '0', }
class InlineHelp(Model): _name = "inline.help" _string = "Help Item" _fields = { "action": fields.Char("Action Name", required=True, search=True), "title": fields.Char("Help Title", required=True, search=True), "content": fields.Text("Help Content", required=True, search=True), "hide": fields.Boolean("Hide"), "create_date": fields.DateTime("Date Created"), "modif_date": fields.DateTime("Date Modified"), } _order = "title" _defaults = { "create_date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"), "modif_date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"), } def create(self, vals, **kw): res = super().create(vals, **kw) static.clear_translations() # XXX: rename this return res def write(self, ids, vals, **kw): super().write(ids, vals, **kw) static.clear_translations() # XXX: rename this def delete(self, ids, **kw): super().delete(ids, **kw) static.clear_translations() # XXX: rename this
class Settings(Model): _name = "hr.payroll.settings" _fields = { "tax_rates": fields.One2Many("hr.tax.rate", "settings_id", "Tax Rates"), "social_rate": fields.Decimal("Rate (%)"), "social_min_wage": fields.Decimal("Min Wage Per Month"), "social_max_wage": fields.Decimal("Max Wage Per Month"), "comments": fields.One2Many("message", "related_id", "Comments"), "social_number": fields.Char("SSO Identification No."), "prov_name": fields.Char("Fund Name"), "child_alw_limit": fields.Integer("Limit to Children"), "child_alw_limit": fields.Integer("Limit to Children"), 'journal_id': fields.Many2One("account.journal", "Journal"), 'bank_account_id': fields.Many2One("account.account", "Bank Account"), 'sso_account_id': fields.Many2One("account.account", "SSO Account"), 'sso_comp_support': fields.Boolean("SSO Company Support"), 'intg_acc': fields.Boolean("Integrate to Account"), }
class PurchaseType(Model): _name = "purchase.type" _string = "Purchase Type" _fields = { "name": fields.Char("Name", required=True), "parent_id": fields.Many2One("purchase.type", "Parent"), "description": fields.Char("Description"), "commission_po": fields.Boolean("Commission Purchase"), "comments": fields.One2Many("message", "related_id", "Comments"), } _order = "name"
class BankAccount(Model): _name = "bank.account" _fields = { "bank_id": fields.Many2One("bank", "Bank", required=True), "branch": fields.Char("Branch"), "number": fields.Char("Account Number", required=True), "signatory": fields.Char("Signatory"), "online": fields.Boolean("Online"), "contact_id": fields.Many2One("contact", "Partner", on_delete="cascade"), }
class Holiday(Model): _name = "hr.holiday" _string = "Holiday" _fields = { "name": fields.Char("Name", search=True), "date": fields.Date("Date", required=True, search=True), "description": fields.Text("Description"), "comments": fields.One2Many("message", "related_id", "Comments"), 'generic': fields.Boolean("Generic"), } _defaults = { "date": lambda *a: time.strftime("%Y-%m-%d"), "comday": False, 'generic': False, } _order = "date" _sql_constraints = [ ("hr_holiday_date_uniq", "unique (date)", "Date should be unique"), ] def get_holidays(self, context={}): date_from = context.get('start_date', time.strftime(FMT_DAY)) date_to = context.get('end_date', time.strftime(FMT_DAY)) cond = [ ['date', '>=', date_from], ['date', '<=', date_to], ] res = set() for r in self.search_read(cond, ['date']): res.update({r['date']}) yearnow, month, date = time.strftime(FMT_DAY).split("-") cond = [['generic', '=', True]] for r in self.search_read(cond, ['date']): y, m, d = r['date'].split("-") date = '-'.join([yearnow, m, d]) res.update({date}) return list(res) def is_holiday(self,ds): d=datetime.strptime(ds,"%Y-%m-%d") w=d.weekday() if w==5 or w==6: return True res=self.search([["date","=",ds]]) if res: return True return False
class Journal(Model): _name = "account.journal" _string = "Journal" _key = ["code"] _fields = { "name": fields.Char("Name", required=True, search=True), "sequence_id": fields.Many2One("sequence", "Sequence", multi_company=True), "comments": fields.One2Many("message", "related_id", "Comments"), "active": fields.Boolean("Active"), "code": fields.Char("Code", search=True), } _defaults = { "active": True, }
class Notification(Model): _name = "hr.notification" _fields = { "subject": fields.Char("Title", required=True), "description": fields.Text("Description"), 'birthday_ntf': fields.Boolean("Birthday Notify"), } def birthday_notify(self, context={}): db = database.get_connection() cr_time = time.strftime("%Y-%m-%d %H:%M:%S") cr_yyyy = cr_time[0:4] cr_mm = cr_time[5:7] cr_dd = cr_time[8:10] today = "%s%s" % (cr_dd, cr_mm) print(cr_time, " checking birthday") subject = "Happy Birth Day" body = subject ntf = get_model("hr.notification").browse(1) if ntf: subject = ntf.subject body = ntf.description count = 0 for emp in get_model("hr.employee").search_browse([['work_status', '=', 'working']]): if emp.birth_date: mm = emp.birth_date[5:7] dd = emp.birth_date[8:10] emp_date = "%s%s" % (dd, mm) if emp_date == today: user_id = emp.user_id.id sql = "select id from message where related_id='hr.employee,%s' and extract(year from create_time) = %s" res = db.query(sql, emp.id, cr_yyyy) if not res: if emp.email: self.trigger([emp.id], "birthday_notify") print("happby birthday %s %s" % (emp.first_name, emp.last_name)) if user_id: vals = { 'subject': subject, 'to_id': user_id, 'body': body, "related_id": "hr.employee,%s" % emp.id, } msg_id = get_model("message").create(vals) print("created message #", msg_id) count += 1 print("SEND TOTOAL: #", count)
class ProfileAccess(Model): _name = "profile.access" _key = ["profile_id", "model_id"] _fields = { "profile_id": fields.Many2One("profile", "Profile", required=True, on_delete="cascade"), "model_id": fields.Many2One("model", "Model", required=True), "perm_read": fields.Boolean("Read"), "perm_create": fields.Boolean("Create"), "perm_write": fields.Boolean("Write"), "perm_delete": fields.Boolean("Delete"), "view_all": fields.Boolean("View All"), "modif_all": fields.Boolean("Modify All"), }
class CreateDB(Model): _name = "create_db" _store = False _fields = { "super_password": fields.Char("Super Admin Password", required=True), "db_name": fields.Char("Database Name", required=True), "admin_password": fields.Char("Admin Password", required=True), "use_demo": fields.Boolean("Use demo data"), } def create_db(self, context={}): data = context["data"] if data["super_password"] != config.get("super_password"): raise Exception("Invalid super admin password") db_name = data["db_name"] admin_password = data["admin_password"] use_demo = data.get("use_demo") if use_demo: base_sql = pkg_resources.resource_string( "netforce_general", "data/base_demo.sql").decode() else: base_sql = pkg_resources.resource_string("netforce_general", "data/base.sql").decode() print("creating db...") db = database.connect("template1") db._db.set_isolation_level(0) db.execute("CREATE DATABASE %s" % db_name) db.close() print("initializing db...") db = database.connect(db_name) db.execute(base_sql) db.execute( "UPDATE base_user SET name='Admin',login='******',password=%s WHERE id=1", admin_password) db.commit() print("done!") return { "next": { "name": "manage_db" }, "flash": "Database created successfully", }
class Language(Model): _name = "language" _string = "Language" _key = ["code"] _fields = { "name": fields.Char("Name", required=True, search=True), "code": fields.Char("Code", required=True), "num_translations": fields.Integer("Number of translations", function="get_num_translations"), "active": fields.Boolean("Active"), "comments": fields.One2Many("message", "related_id", "Comments"), } _defaults = { "active": True, } def create(self, vals, **kw): res = super().create(vals, **kw) static.clear_translations() return res def write(self, ids, vals, **kw): super().write(ids, vals, **kw) static.clear_translations() def delete(self, ids, **kw): super().delete(ids, **kw) static.clear_translations() def get_num_translations(self, ids, context={}): db = database.get_connection() res = db.query( "SELECT lang_id,COUNT(*) AS num FROM translation WHERE lang_id IN %s GROUP BY lang_id", tuple(ids)) vals = {r.lang_id: r.num for r in res} return vals def get_active_langs(self): db = database.get_connection() res = db.query("SELECT code,name FROM language WHERE active=true ORDER BY name") active_langs = [dict(r) for r in res] return active_langs
class Model(Model): _name = "model" _string = "Model" _name_field = "string" _key = ["name"] _fields = { "name": fields.Char("Name", required=True, search=True), "string": fields.Char("String", required=True, search=True), "fields": fields.One2Many("field", "model_id", "Fields"), "code": fields.Text("Code"), "order": fields.Char("Order"), "description": fields.Text("Description"), "offline": fields.Boolean("Offline"), "module_id": fields.Many2One("module", "Module"), } _order = "name" def name_search_multi(self, name, models, condition=[], limit=None, context={}): for model in models: m = get_model(model) res = m.name_search(name, condition=condition, limit=limit, context=context) if res: return { "model": model, "values": res, } return { "model": None, "values": [], }
class TaxComponent(Model): _name = "account.tax.component" _fields = { "tax_rate_id": fields.Many2One("account.tax.rate", "Tax Rate", required=True, on_delete="cascade"), "name": fields.Char("Name", required=True), "compound": fields.Boolean("Compound"), "rate": fields.Decimal("Rate", required=True), "account_id": fields.Many2One("account.account", "Account", multi_company=True), "type": fields.Selection( [["vat", "VAT"], ["vat_exempt", "VAT Exempt"], ["vat_defer", "Deferred VAT"], ["wht", "Withholding Tax"]], "Tax Type"), "trans_type": fields.Selection([["out", "Sale"], ["in", "Purchase"]], "Transaction Type"), "description": fields.Text("Description"), } _defaults = { "rate": 0, } def name_get(self, ids, context={}): vals = [] for obj in self.browse(ids): name = "%s - %s" % (obj.tax_rate_id.name, obj.name) vals.append((obj.id, name)) return vals
class Cart(Model): _name = "ecom2.cart" _string = "Cart" _name_field = "number" _audit_log = True _fields = { "number": fields.Char("Number", required=True, search=True), "date": fields.DateTime("Date Created", required=True, search=True), "customer_id": fields.Many2One("contact", "Customer", search=True), "lines": fields.One2Many("ecom2.cart.line", "cart_id", "Lines"), "ship_amount_details": fields.Json("Shipping Amount Details", function="get_ship_amount_details"), "amount_ship": fields.Decimal("Shipping Amount", function="get_amount_ship"), "amount_total": fields.Decimal("Total Amount", function="get_total"), "sale_orders": fields.One2Many("sale.order", "related_id", "Sales Orders"), "delivery_date": fields.Date("Delivery Date"), "ship_address_id": fields.Many2One("address", "Shipping Address"), "bill_address_id": fields.Many2One("address", "Billing Address"), "delivery_slot_id": fields.Many2One("delivery.slot", "Peferred Delivery Slot"), "ship_method_id": fields.Many2One("ship.method", "Shipping Method"), "pay_method_id": fields.Many2One("payment.method", "Payment Method"), "logs": fields.One2Many("log", "related_id", "Audit Log"), "state": fields.Selection([["draft", "Draft"], ["confirmed", "Confirmed"], ["canceled", "Canceled"]], "Status", required=True), "payment_methods": fields.Json("Payment Methods", function="get_payment_methods"), "delivery_delay": fields.Integer("Delivery Delay (Days)", function="get_delivery_delay"), "delivery_slots": fields.Json("Delivery Slots", function="get_delivery_slots"), "delivery_slots_str": fields.Text("Delivery Slots", function="get_delivery_slots_str"), "date_delivery_slots": fields.Json("Date Delivery Slots", function="get_date_delivery_slots"), "comments": fields.Text("Comments"), "transaction_no": fields.Char("Payment Transaction No.", search=True), "currency_id": fields.Many2One("currency", "Currency", required=True), "invoices": fields.One2Many("account.invoice", "related_id", "Invoices"), "company_id": fields.Many2One("company", "Company"), "voucher_id": fields.Many2One("sale.voucher", "Voucher"), "ship_addresses": fields.Json("Shipping Addresses", function="get_ship_addresses"), "amount_voucher": fields.Decimal("Voucher Amount", function="get_amount_voucher", function_multi=True), "voucher_error_message": fields.Text("Voucher Error Message", function="get_amount_voucher", function_multi=True), "free_ship_address": fields.Boolean("Is free Ship"), } _order = "date desc" def _get_number(self, context={}): seq_id = get_model("sequence").find_sequence(type="ecom_cart") 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 = 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, context=context) def _get_currency(self, context={}): res = get_model("company").search([]) # XXX if not res: return company_id = res[0] access.set_active_company(company_id) settings = get_model("settings").browse(1) return settings.currency_id.id def _get_company(self, context={}): res = get_model("company").search([]) # XXX if res: return res[0] def _get_ship_method(self, context={}): res = get_model("ship.method").search([], order="sequence") if res: return res[0] _defaults = { "date": lambda *a: time.strftime("%Y-%m-%d %H:%M:%S"), "number": _get_number, "state": "draft", "currency_id": _get_currency, "company_id": _get_company, "ship_method_d": _get_ship_method, "free_ship_address": False, } def get_ship_amount_details(self, ids, context={}): print("get_ship_amount_details", ids) vals = {} for obj in self.browse(ids): delivs = [] for line in obj.lines: date = line.delivery_date meth_id = line.ship_method_id.id or line.cart_id.ship_method_id.id addr_id = line.ship_address_id.id or line.cart_id.ship_address_id.id if not date or not meth_id or not addr_id: continue delivs.append((date, meth_id, addr_id)) delivs = list(set(delivs)) details = [] for date, meth_id, addr_id in delivs: ctx = { "contact_id": obj.customer_id.id, "ship_address_id": addr_id, } meth = get_model("ship.method").browse(meth_id, context=ctx) details.append({ "ship_addr_id": addr_id, "date": date, "ship_method_id": meth.id, "ship_amount": meth.ship_amount, }) vals[obj.id] = details return vals def get_amount_ship(self, ids, context={}): print("get_amount_ship", ids) vals = {} for obj in self.browse(ids): ship_amt = 0 for d in obj.ship_amount_details: ship_amt += d["ship_amount"] or 0 vals[obj.id] = ship_amt return vals def get_total(self, ids, context={}): vals = {} for obj in self.browse(ids): amt = 0 for line in obj.lines: amt += line.amount vals[obj.id] = amt + obj.amount_ship - obj.amount_voucher return vals def get_payment_methods(self, ids, context={}): res = [] for obj in get_model("payment.method").search_browse([]): res.append({ "id": obj.id, "name": obj.name, }) return {ids[0]: res} def confirm(self, ids, context={}): obj = self.browse(ids[0]) user_id = context.get("user_id") # XX: remove this if user_id: user_id = int(user_id) user = get_model("base.user").browse(user_id) if user.contact_id: obj.write({"customer_id": user.contact_id.id}) access.set_active_company(1) # XXX vals = { "contact_id": obj.customer_id.id, "ship_address_id": obj.ship_address_id.id, "ship_method_id": obj.ship_method_id.id, "bill_address_id": obj.bill_address_id.id, "due_date": obj.delivery_date, "lines": [], "related_id": "ecom2.cart,%s" % obj.id, "delivery_slot_id": obj.delivery_slot_id.id, "pay_method_id": obj.pay_method_id.id, "other_info": obj.comments, "voucher_id": obj.voucher_id.id, "ref": obj.comments, # XXX } print(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Ship Method Id ", ship_method_id) for line in obj.lines: prod = line.product_id if line.lot_id and line.qty_avail <= 0: raise Exception("Lot is out of stock (%s)" % prod.name) if not prod.locations: raise Exception("Can't find location for product %s" % prod.code) location_id = prod.locations[0].location_id.id line_vals = { "product_id": prod.id, "description": prod.description, "qty": line.qty, "uom_id": line.uom_id.id, "unit_price": line.unit_price, "location_id": location_id, "lot_id": line.lot_id.id, "due_date": line.delivery_date, "delivery_slot_id": obj.delivery_slot_id.id, "ship_address_id": line.ship_address_id.id, } vals["lines"].append(("create", line_vals)) for ship in obj.ship_amount_details: meth_id = ship["ship_method_id"] amount = ship["ship_amount"] meth = get_model("ship.method").browse(meth_id) prod = meth.product_id if not prod: raise Exception("Missing product in shipping method %s" % meth.name) line_vals = { "product_id": prod.id, "description": prod.description, "qty": 1, "uom_id": prod.uom_id.id, "unit_price": amount, } vals["lines"].append(("create", line_vals)) sale_id = get_model("sale.order").create(vals) sale = get_model("sale.order").browse(sale_id) sale.confirm() obj.write({"state": "confirmed"}) obj.trigger("confirm_order") return { "sale_id": sale_id, } def cancel_order(self, ids, context={}): obj = self.browse(ids[0]) for sale in obj.sale_orders: if sale.state == "voided": continue for inv in sale.invoices: if inv.state != "voided": raise Exception( "Can not cancel order %s because there are linked invoices" % sale.number) for pick in sale.pickings: if pick.state == "voided": continue pick.void() sale.void() for inv in obj.invoices: if inv.state != "voided": raise Exception( "Can not cancel cart %s because there are linked invoices" % obj.number) obj.write({"state": "canceled"}) def payment_received(self, ids, context={}): obj = self.browse(ids[0]) if obj.state != "waiting_payment": raise Exception("Cart is not waiting payment") res = obj.sale_orders.copy_to_invoice() inv_id = res["invoice_id"] inv = get_model("account.invoice").browse(inv_id) inv.write({"related_id": "ecom2.cart,%s" % obj.id}) inv.post() method = obj.pay_method_id if not method: raise Exception("Missing payment method in cart %s" % obj.number) if not method.account_id: raise Exception("Missing account in payment method %s" % method.name) transaction_no = context.get("transaction_no") pmt_vals = { "type": "in", "pay_type": "invoice", "contact_id": inv.contact_id.id, "account_id": method.account_id.id, "lines": [], "company_id": inv.company_id.id, "transaction_no": transaction_no, } line_vals = { "invoice_id": inv_id, "amount": inv.amount_total, } pmt_vals["lines"].append(("create", line_vals)) pmt_id = get_model("account.payment").create(pmt_vals, context={"type": "in"}) get_model("account.payment").post([pmt_id]) obj.write({"state": "paid"}) for sale in obj.sale_orders: for pick in sale.pickings: if pick.state == "pending": pick.approve() def to_draft(self, ids, context={}): obj = self.browse(ids[0]) obj.write({"state": "draft"}) def set_qty(self, ids, prod_id, qty, context={}): print("Cart.set_qty", ids, prod_id, qty) obj = self.browse(ids[0]) line_id = None for line in obj.lines: if line.product_id.id == prod_id and not line.lot_id: line_id = line.id break if line_id: if qty == 0: get_model("ecom2.cart.line").delete([line_id]) else: get_model("ecom2.cart.line").write([line_id], {"qty": qty}) else: if qty != 0: get_model("ecom2.cart.line").create({ "cart_id": obj.id, "product_id": prod_id, "qty": qty }) def set_date_qty(self, ids, due_date, prod_id, qty, context={}): print("Cart.set_date_qty", ids, due_date, prod_id, qty) obj = self.browse(ids[0]) line_id = None for line in obj.lines: if line.delivery_date == due_date and line.product_id.id == prod_id and not line.lot_id: line_id = line.id break if line_id: if qty == 0: get_model("ecom2.cart.line").delete([line_id]) else: get_model("ecom2.cart.line").write([line_id], {"qty": qty}) else: if qty != 0: ctx = { "cart_id": obj.id, "delivery_date": due_date, "product_id": prod_id } get_model("ecom2.cart.line").create( { "cart_id": obj.id, "product_id": prod_id, "qty": qty, "delivery_date": due_date }, context=ctx) def add_lot(self, ids, prod_id, lot_id, context={}): print("Cart.add_lot", ids, prod_id, lot_id) obj = self.browse(ids[0]) line_id = None for line in obj.lines: if line.product_id.id == prod_id and line.lot_id.id == lot_id: line_id = line.id break if line_id: raise Exception("Lot already added to cart") get_model("ecom2.cart.line").create({ "cart_id": obj.id, "product_id": prod_id, "lot_id": lot_id, "qty": 1 }) def remove_lot(self, ids, prod_id, lot_id, context={}): obj = self.browse(ids[0]) line_id = None for line in obj.lines: if line.product_id.id == prod_id and line.lot_id.id == lot_id: line_id = line.id break if not line_id: raise Exception("Lot not found in cart") get_model("ecom2.cart.line").delete([line_id]) def set_qty_auto_select_lot(self, ids, prod_id, qty, context={}): print("Cart.set_qty_auto_select_lot", ids, prod_id, qty) obj = self.browse(ids[0]) cur_qty = 0 for line in obj.lines: if line.product_id.id == prod_id: cur_qty += line.qty diff_qty = qty - cur_qty if diff_qty > 0: self.add_lots_auto_select(ids, prod_id, diff_qty, context=context) elif diff_qty < 0: self.remove_lots_auto_select(ids, prod_id, -diff_qty, context=context) def add_lots_auto_select(self, ids, prod_id, add_qty, context={}): print("Cart.add_lots_auto_select", ids, prod_id, add_qty) obj = self.browse(ids[0]) exclude_lot_ids = [] for line in obj.lines: if line.product_id.id == prod_id and line.lot_id: exclude_lot_ids.append(line.lot_id.id) prod = get_model("product").browse(prod_id) avail_qty = prod.stock_qty add_lot_ids = [] for lot in prod.stock_lots: if len(add_lot_ids) >= avail_qty: break if lot.id not in exclude_lot_ids: add_lot_ids.append(lot.id) if len(add_lot_ids) >= add_qty: break print("add_lot_ids", add_lot_ids) for lot_id in add_lot_ids: get_model("ecom2.cart.line").create({ "cart_id": obj.id, "product_id": prod_id, "lot_id": lot_id, "qty": 1 }) remain_qty = add_qty - len(add_lot_ids) print("remain_qty", remain_qty) if remain_qty > 0: found_line = None for line in obj.lines: if line.product_id.id == prod_id and not line.lot_id: found_line = line break if found_line: found_line.write({"qty": found_line.qty + remain_qty}) else: get_model("ecom2.cart.line").create({ "cart_id": obj.id, "product_id": prod_id, "qty": remain_qty }) def remove_lots_auto_select(self, ids, prod_id, remove_qty, context={}): print("Cart.remove_lots_auto_select", ids, prod_id, remove_qty) obj = self.browse(ids[0]) remain_qty = remove_qty for line in obj.lines: if line.product_id.id == prod_id and not line.lot_id: if line.qty <= remain_qty: remain_qty -= line.qty line.delete() else: line.write({"qty": line.qty - remain_qty}) remain_qty = 0 print("remain_qty", remain_qty) if remain_qty > 0: lots = [] for line in obj.lines: if line.product_id.id == prod_id and line.lot_id: d = line.lot_id.received_date or "1900-01-01" lots.append((d, line.id)) lots.sort() del_ids = [] for d, line_id in lots: del_ids.append(line_id) if len(del_ids) >= remain_qty: break print("del_ids", del_ids) get_model("ecom2.cart.line").delete(del_ids) def get_delivery_delay(self, ids, context={}): vals = {} for obj in self.browse(ids): vals[obj.id] = max(l.delivery_delay for l in obj.lines) if obj.lines else 0 return vals def get_delivery_slots(self, ids, context={}): print("get_delivery_slots", ids) obj = self.browse(ids[0]) settings = get_model("ecom2.settings").browse(1) max_days = settings.delivery_max_days if not max_days: return {obj.id: []} min_hours = settings.delivery_min_hours or 0 d_from = date.today() + timedelta(days=obj.delivery_delay) d_to = d_from + timedelta(days=max_days) d = d_from slots = [] for slot in get_model("delivery.slot").search_browse([]): slots.append([slot.id, slot.name, slot.time_from]) slot_num_sales = {} for sale in get_model("sale.order").search_browse( [["plr_order_type", "=", "grocery"], ["date", ">=", time.strftime("%Y-%m-%d")]]): k = (sale.due_date, sale.delivery_slot_id.id) slot_num_sales.setdefault(k, 0) slot_num_sales[k] += 1 print("slot_num_sales", slot_num_sales) slot_caps = {} for cap in get_model("delivery.slot.capacity").search_browse([]): k = (cap.slot_id.id, int(cap.weekday)) slot_caps[k] = cap.capacity print("slot_caps", slot_caps) delivery_weekdays = None for line in obj.lines: prod = line.product_id if prod.delivery_weekdays: days = [int(w) for w in prod.delivery_weekdays.split(",")] if delivery_weekdays == None: delivery_weekdays = days else: delivery_weekdays = [ d for d in delivery_weekdays if d in days ] days = [] now = datetime.now() tomorrow = now + timedelta(days=1) tomorrow_seconds = 0 if settings.work_time_start and settings.work_time_end: today_end = datetime.strptime( date.today().strftime("%Y-%m-%d") + " " + settings.work_time_end, "%Y-%m-%d %H:%M") tomorrow_start = datetime.strptime( tomorrow.strftime("%Y-%m-%d") + " " + settings.work_time_start, "%Y-%m-%d %H:%M") if now < today_end and now.weekday() != 6: remain_seconds = (today_end - now).total_seconds() else: remain_seconds = 0 if remain_seconds < min_hours * 3600: tomorrow_seconds = min_hours * 3600 - remain_seconds print("tomorrow_seconds=%s" % tomorrow_seconds) while d <= d_to: ds = d.strftime("%Y-%m-%d") res = get_model("hr.holiday").search([["date", "=", ds]]) if res: d += timedelta(days=1) continue w = d.weekday() if w == 6 or delivery_weekdays is not None and w not in delivery_weekdays: d += timedelta(days=1) continue day_slots = [] for slot_id, slot_name, from_time in slots: t_from = datetime.strptime(ds + " " + from_time + ":00", "%Y-%m-%d %H:%M:%S") capacity = slot_caps.get((slot_id, w)) num_sales = slot_num_sales.get((ds, slot_id), 0) state = "avail" if d == now.date(): if t_from < now or (t_from - now).total_seconds( ) < min_hours * 3600 or tomorrow_seconds: state = "full" elif d == tomorrow.date() and tomorrow_seconds: if (t_from - tomorrow_start).total_seconds() < tomorrow_seconds: state = "full" if capacity is not None and num_sales >= capacity: state = "full" day_slots.append( [slot_id, slot_name, state, num_sales, capacity]) days.append([ds, day_slots]) d += timedelta(days=1) print("days:") pprint(days) return {obj.id: days} def get_delivery_slots_str(self, ids, context={}): vals = {} for obj in self.browse(ids): s = "" for d, slots in obj.delivery_slots: s += "- Date: %s\n" % d for slot_id, name, state, num_sales, capacity in slots: s += " - %s: %s (%s/%s)\n" % (name, state, num_sales, capacity or "-") vals[obj.id] = s return vals def get_date_delivery_slots(self, ids, context={}): print("get_date_delivery_slots", ids) obj = self.browse(ids[0]) slots = [] for slot in get_model("delivery.slot").search_browse([]): slots.append([slot.id, slot.name]) dates = [] for line in obj.lines: d = line.delivery_date if d: dates.append(d) dates = list(set(dates)) date_slots = {} for d in dates: date_slots[d] = slots # TODO: use capacity? return {obj.id: date_slots} def get_ship_addresses(self, ids, context={}): obj = self.browse(ids[0]) settings = get_model("ecom2.settings").browse(1) contact = obj.customer_id addrs = [] if contact: for a in contact.addresses: addr_vals = { "id": a.id, "name": a.address, } if obj.ship_method_id: # TODO: handle general case for different shipping methods per order meth_id = obj.ship_method_id.id ctx = {"ship_address_id": a.id} meth = get_model("ship.method").browse(meth_id, context=ctx) addr_vals["ship_amount"] = meth.ship_amount else: addr_vals["ship_amount"] = 0 addrs.append(addr_vals) for a in settings.extra_ship_addresses: addr_vals = { "id": a.id, "name": a.address or "", } if obj.ship_method_id: meth_id = obj.ship_method_id.id ctx = {"ship_address_id": a.id} meth = get_model("ship.method").browse(meth_id, context=ctx) addr_vals["ship_amount"] = meth.ship_amount else: addr_vals["ship_amount"] = 0 addrs.append(addr_vals) return {obj.id: addrs} def apply_voucher_code(self, ids, voucher_code, context={}): obj = self.browse(ids[0]) res = get_model("sale.voucher").search([["code", "=", voucher_code]]) if not res: raise Exception("Invalid voucher code") voucher_id = res[0] obj.write({"voucher_id": voucher_id}) def clear_voucher(self, ids, context={}): obj = self.browse(ids[0]) obj.write({"voucher_id": None}) def get_amount_voucher(self, ids, context={}): print("get_amount_voucher", ids) vals = {} for obj in self.browse(ids): voucher = obj.voucher_id if voucher: ctx = { "contact_id": obj.customer_id.id, "amount_total": 0, "products": [], } for line in obj.lines: ctx["amount_total"] += line.amount ctx["products"].append({ "product_id": line.product_id.id, "unit_price": line.unit_price, "qty": line.qty, "uom_id": line.uom_id.id, "amount": line.amount, }) ctx["amount_total"] += obj.amount_ship res = voucher.apply_voucher(context=ctx) disc_amount = res.get("discount_amount", 0) error_message = res.get("error_message") else: disc_amount = 0 error_message = None vals[obj.id] = { "amount_voucher": disc_amount, "voucher_error_message": error_message, } return vals def update_date_delivery(self, ids, date, vals, context={}): print("cart.update_date_delivery", ids, date, vals) obj = self.browse(ids[0]) settings = get_model("ecom2.settings").browse(1) for line in obj.lines: if line.delivery_date == date: line.write(vals) #for a in settings.extra_ship_addresses: #if a.id == vals['ship_address_id']: #return {'free_ship':True} return {'free_ship': False} def empty_cart(self, ids, context={}): obj = self.browse(ids[0]) obj.write({"lines": [("delete_all", )]}) def check_due_dates(self, ids, context={}): obj = self.browse(ids[0]) today = time.strftime("%Y-%m-%d") if obj.delivery_date and obj.delivery_date < today: raise Exception("Delivery date is in the past %s" % obj.delivery_date) for line in obj.lines: if line.delivery_date and line.delivery_date < today: raise Exception( "Delivery date is in the past %s for product %s" % (line.delivery_date, line.product_id.name)) def free_ship_address(self, ids, context={}): obj = self.browse(ids[0]) settings = get_model("ecom2.settings").browse(1) data = [] for a in settings.extra_ship_addresses: if obj.ship_method_id: data["is_free"] = False else: data["is_free"] = True data.append(data) return {obj.id: addrs}
class Employee(Model): _name = "hr.employee" _string = "Employee" _name_field = "first_name" # XXX _multi_company = True _key = ["code", "company_id"] _export_field = "code" _fields = { "code": fields.Char("Employee Code", search=True), "department_id": fields.Many2One("hr.department", "Department", search=True), "title": fields.Selection( [["mr", "Mr."], ["mrs", "Mrs."], ["miss", "Miss"], ["ms", "Ms."]], "Title"), "first_name": fields.Char("First Name", search=True, translate=True), "last_name": fields.Char("Last Name", required=True, search=True, translate=True), "hire_date": fields.Date("Hire Date"), "work_status": fields.Selection([["working", "Working"], ["dismissed", "Dismissed"], ["resigned", "Resigned"], ["died", "Died"]], "Work Status"), "work_type": fields.Selection( [["monthly", "Monthly"], ["daily", "Daily"], ["hourly", "Job"]], "Work Type"), "resign_date": fields.Date("Resign Date"), "position": fields.Char("Position", search=True), "birth_date": fields.Date("Birth Date"), "age": fields.Integer("Age", function="get_age"), "gender": fields.Selection([["male", "Male"], ["female", "Female"]], "Gender"), "marital_status": fields.Selection([["single", "Single"], ["married", "Married"], ["divorced", "Divorced"], ["widowed", "Widowed"]], "Marital Status"), "addresses": fields.One2Many("address", "employee_id", "Address"), "id_no": fields.Char("ID No."), "drive_license_type": fields.Selection([["car", "Car"], ['motorcycle', 'Motorcycle']], "Driving License"), "drive_license_no": fields.Char("Driving License No."), "country_id": fields.Many2One("country", "Country"), "bank_account": fields.Char("Bank Account"), "salary": fields.Decimal("Salary"), "picture": fields.File("Picture"), "tax_no": fields.Char("Taxpayer ID No."), "spouse_first_name": fields.Char("Spouse First Name"), "spouse_last_name": fields.Char("Spouse Last Name"), "spouse_title": fields.Selection([["mr", "Mr."], ["ms", "Ms."]], "Spouse Title"), "spouse_birth_date": fields.Date("Spouse Birth Date"), "spouse_tax_no": fields.Char("Spouse Tax ID No"), "spouse_status": fields.Selection( [["married", "Married existed throughout this tax year"], ["married_new", "Married during this tax year"], ["divorced", "Divorced during tax year"], ["deceased", "Deceased during tax year"]], "Spouse Status"), "spouse_filing_status": fields.Selection( [["joint", "Has income and file joint return"], ["separate", "Has income and file separate tax return"], ["no_income", "Has no income"]], "Spouse Filing Status"), "num_child1": fields.Integer("No. of Children #1 (C3)"), "num_child2": fields.Integer("No. of Children #2 (C3)"), "social_no": fields.Char("Social No."), "social_register": fields.Boolean("Register Soc. Secur."), "social_calc_method": fields.Selection( [["regular", "Regular Rate"], ["none", "Not Participate"], ["special", "Special Rate"]], "Calc. Method"), "prov_fund_no": fields.Char("Prov. Fund No."), "prov_open_date": fields.Char("Opened Prov. Fund A/C Date"), "prov_rate_employer": fields.Decimal("Employer Contribution (%)"), "prov_rate_employee": fields.Decimal("Employee Contribution (%)"), "gov_pension_fund": fields.Decimal("Gov. Pension Fund Amount (B2)"), "teacher_fund": fields.Decimal("Teacher Aid Fund Amount (B3)"), "old_disabled": fields.Decimal("Older than 65 or disabled (personal, B4)"), "old_disabled_spouse": fields.Decimal("Older than 65 or disabled (spouse, B5)"), "severance_pay": fields.Decimal("Severance Pay (B6)"), "education_donation": fields.Decimal("Education Donations (A8)"), "other_donation": fields.Decimal("Other Donations (A10)"), "house_deduct": fields.Decimal("Exemption for home buyer (A13)"), "wht_amount": fields.Decimal("Withholding Tax Amount (A15)"), "father_id_no": fields.Char("Father ID No. (C4)"), "mother_id_no": fields.Char("Mother ID No. (C4)"), "spouse_father_id_no": fields.Char("Father of spouse ID No. (C4)"), "spouse_mother_id_no": fields.Char("Mother of spouse ID No. (C4)"), "disabled_support": fields.Decimal("Disabled person support (C5)"), "parent_health_insurance": fields.Decimal("Parent Health Insurance (C6)"), "life_insurance": fields.Decimal("Life Insurance (C7)"), "retirement_mutual_fund": fields.Decimal("Retirement Mutual Fund (C9)"), "long_term_equity_fund": fields.Decimal("Long Term Equity Fund (C10)"), "interest_residence": fields.Decimal("Interest paid for residence (C11)"), "other_deduct": fields.Decimal("Other Deductions (C12)"), "comments": fields.One2Many("message", "related_id", "Comments"), "active": fields.Boolean("Active"), "time_in": fields.DateTime("Last Sign In", function="get_attend", function_multi=True), "time_out": fields.DateTime("Last Sign Out", function="get_attend", function_multi=True), "attend_state": fields.Selection([["absent", "Absent"], ["present", "Present"]], "Status", function="get_attend", function_multi=True), "user_id": fields.Many2One("base.user", "User", search=True), "payslips": fields.One2Many("hr.payslip", "employee_id", "Payslips"), "documents": fields.One2Many("document", "related_id", "Documents"), "phone": fields.Char("Phone", search=True), "approver_id": fields.Many2One("base.user", "Approver"), "company_id": fields.Many2One("company", "Company"), "leave_types": fields.Many2Many("hr.leave.type", "Leave Types"), "attendance_id": fields.Integer("Attendance ID"), "email": fields.Char("Email", search=True), 'profile_id': fields.Many2One("hr.payitem.profile", "Pay Item Profile"), 'schedule_id': fields.Many2One("hr.schedule", "Working Schedule"), 'leaves': fields.One2Many('hr.leave', 'employee_id', 'Leaves'), } def _get_code(self, context={}): while 1: code = get_model("sequence").get_number("employee") if not code: return None res = self.search([["code", "=", code]]) if not res: return code get_model("sequence").increment("employee") _defaults = { "active": True, "work_status": "working", "code": _get_code, "company_id": lambda *a: get_active_company(), } _order = "code,first_name,last_name" def name_get(self, ids, context={}): vals = [] for obj in self.browse(ids): if obj.first_name: name = obj.first_name + " " + obj.last_name else: name = obj.last_name if obj.code: name += " [%s]" % obj.code vals.append((obj.id, name)) return vals def name_search(self, name, condition=[], limit=None, context={}): cond = [[ "or", ["first_name", "ilike", "%" + name + "%"], ["last_name", "ilike", "%" + name + "%"], ["code", "ilike", "%" + name + "%"] ], condition] ids = self.search(cond, limit=limit) return self.name_get(ids, context) def get_age(self, ids, context={}): vals = {} cr_year = int(time.strftime('%Y')) for obj in self.browse(ids): if obj.birth_date: age = cr_year - int(obj.birth_date[0:4]) else: age = 0 vals[obj.id] = age return vals def get_attend(self, ids, context={}): vals = {} for obj in self.browse(ids): # user_id=obj.user_id.id # if user_id: # db=database.get_connection() #res=db.get("SELECT MAX(time) AS time_in FROM hr_attendance WHERE user_id=%s AND action='sign_in'",user_id) # time_in=res.time_in #res=db.get("SELECT MAX(time) AS time_out FROM hr_attendance WHERE user_id=%s AND action='sign_out'",user_id) # time_out=res.time_out # else: # time_in=None # time_out=None db = database.get_connection() res = db.get( "SELECT MAX(time) AS time_in FROM hr_attendance WHERE employee_id=%s AND action='sign_in'", obj.id) time_in = res.time_in res = db.get( "SELECT MAX(time) AS time_out FROM hr_attendance WHERE employee_id=%s AND action='sign_out'", obj.id) time_out = res.time_out if time_in: if time_out and time_out > time_in: state = "absent" else: today = time.strftime("%Y-%m-%d") if time_in.startswith(today): state = "present" else: state = "absent" # should not show timeout of anotherday # if not time_out.startswith(today): # time_out=None else: state = "absent" vals[obj.id] = { "time_in": time_in, "time_out": time_out, "attend_state": state, } return vals def get_address(self, ids, context={}): obj = self.browse(ids)[0] if not obj.addresses: return "" addr = obj.addresses[0] res = addr.get_address_text() return res[addr.id] def onchange_num_child(self, context={}): data = context["data"] setting = get_model("hr.payroll.settings").browse(1) child_alw_limit = setting.child_alw_limit or 0 child_total = (data['num_child1'] or 0) + (data['num_child2'] or 0) if child_alw_limit and child_total > child_alw_limit: data['num_child1'] = 0 data['num_child2'] = 0 return data
class TaxRate(Model): _name = "account.tax.rate" _string = "Tax Rate" _key = ["name"] _name_field = "name" _fields = { "name": fields.Char("Name", required=True, search=True), "code": fields.Char("Code", search=True), "type": fields.Selection([["out", "Sale"], ["in", "Purchase"]], "Type", required=True, search=True), #Use For Filter "rate": fields.Decimal("Tax Rate", function="get_rate", function_multi=True), "wht_rate": fields.Decimal("WHT Rate", function="get_rate", function_multi=True), "components": fields.One2Many("account.tax.component", "tax_rate_id", "Components"), "uuid": fields.Char("UUID"), "active": fields.Boolean("Active"), "comments": fields.One2Many("message", "related_id", "Comments"), "payer": fields.Selection( [["wht", "Withhold at source"], ["every_time", "Pay every time"], ["one_time", "Pay one time"], ["other", "Others"]], "Payer"), "note": fields.Text("Note"), } _defaults = { "uuid": lambda *a: str(uuid.uuid4()), "active": True, } _order = "name" def get_rate(self, ids, context={}): vals = {} for obj in self.browse(ids): rate = Decimal(0) wht_rate = Decimal(0) for comp in obj.components: if comp.type == "vat": rate += comp.rate elif comp.type == "wht": wht_rate += comp.rate res = {"rate": rate, "wht_rate": wht_rate} vals[obj.id] = res return vals def update_total(self, context={}): data = context["data"] data["rate"] = 0 for comp in data["components"]: data["rate"] += comp["rate"] return data # XXX: remove this def compute_tax(self, tax_id, amt, tax_type="tax_ex", wht=False): pc_factor = Decimal(100) if not tax_id: return 0 if tax_type == "no_tax": return 0 obj = self.browse(tax_id) vat_rate = Decimal(0) wht_rate = Decimal(0) for comp in obj.components: if comp.type == "wht": wht_rate += comp.rate or 0 elif comp.type == "vat": vat_rate += comp.rate or 0 base_amt = Decimal(0) if tax_type == "tax_ex": base_amt = amt or 0 elif tax_type == "tax_in": base_amt = (amt or 0) / (1 + vat_rate / pc_factor) base_amt = Decimal(base_amt) wht_rate = Decimal(wht_rate) vat_rate = Decimal(vat_rate) if wht: return base_amt * wht_rate / pc_factor else: return base_amt * vat_rate / pc_factor # XXX: remove this # (not used in payment) def compute_components(self, tax_id, amt, tax_type="tax_ex", when="invoice"): assert (when != "payment") # XXX if tax_type == "no_tax": return {} obj = self.browse(tax_id) if tax_type == "tax_in": base_amt = amt / (1 + obj.rate / 100) else: base_amt = amt has_defer = False for comp in obj.components: if comp.type == "vat_defer": has_defer = True comps = {} for comp in obj.components: if comp.type == "wht": continue if has_defer and comp.type == "vat": continue comps[comp.id] = base_amt * (comp.rate / 100) return comps def compute_base(self, tax_id, amt=0, tax_type="tax_ex"): if isinstance(tax_id, BrowseRecord): # XXX: for speed (use browse cache) obj = tax_id else: obj = self.browse(tax_id) amt = Decimal(amt) base_amt = amt # default if tax_type == "tax_in": base_amt = amt / (1 + obj.rate / 100) elif tax_type == "tax_ex": base_amt = amt return base_amt # TODO: use this in invoice/claim def compute_taxes(self, tax_id, base, when="invoice"): if isinstance(tax_id, BrowseRecord): # XXX: for speed (use browse cache) obj = tax_id else: obj = self.browse(tax_id) has_defer = False for comp in obj.components: if comp.type == "vat_defer": has_defer = True comps = {} for comp in obj.components: if when == "invoice": if comp.type in ("vat", "vat_exempt") and has_defer: continue if comp.type == "wht": continue elif when == "invoice_payment": if comp.type in ("vat", "vat_exempt") and not has_defer: continue elif when == "invoice_payment_inv": if comp.type != "vat_defer": continue elif when == "invoice_payment_pmt": if comp.type in ("vat", "vat_exempt") and not has_defer: continue if comp.type == "vat_defer": continue elif when == "direct_payment": if comp.type == "vat_defer": continue else: raise Exception("Can't compute taxes: invalid 'when'") if when == "invoice" and comp.type not in ("vat", "vat_exempt", "vat_defer"): continue if when == "payment" and comp.type != "wht": continue tax = base * (comp.rate / 100) if comp.type == "wht": tax = -tax elif comp.type == "vat_defer" and when in ("invoice_payment", "invoice_payment_inv"): tax = -tax comps[comp.id] = tax return comps def has_defer_vat(self, ids, context={}): for obj in self.browse(ids): for comp in obj.components: if comp.type == "vat_defer": return True return False def copy(self, ids, context={}): obj = self.browse(ids)[0] vals = { "name": obj.name + "-copy", "code": obj.code, "type": obj.type, "payer": obj.payer, "note": obj.note, "rate": obj.rate, "wht_rate": obj.wht_rate, "components": [], "uuid": str(uuid.uuid4()), "active": True, } for comp in obj.components: comp_vals = { "name": comp.name, "compound": comp.compo4und, "rate": comp.rate, "account_id": comp.account_id.id, "type": comp.type, "trans_type": comp.trans_type, "description": comp.description, } vals["components"].append(("create", comp_vals)) new_id = self.create(vals) new_obj = self.browse(new_id) return { "next": { "name": "tax_rate", "mode": "form", "active_id": new_id, }, "flash": "Tax rate copied", }
class ReportStockCard(Model): _name = "report.stock.card" _transient = True _fields = { "date_from": fields.Date("From", required=True), "date_to": fields.Date("To", required=True), "product_id": fields.Many2One("product", "Product", on_delete="cascade"), "categ_id": fields.Many2One("product.categ", "Product Category", on_delete="cascade"), "location_id": fields.Many2One("stock.location", "Location", on_delete="cascade"), "uom_id": fields.Many2One("uom", "UoM"), "lot_id": fields.Many2One("stock.lot", "Lot / Serial Number"), "invoice_id": fields.Many2One("account.invoice", "Invoice"), "show_pending": fields.Boolean("Show Pending"), "show_qty2": fields.Boolean("Show Secondary Qty"), "hide_zero": fields.Boolean("Hide Zero Lines"), } _defaults = { "date_from": lambda *a: date.today().strftime("%Y-%m-01"), "date_to": lambda *a: (date.today() + relativedelta(day=31)).strftime("%Y-%m-%d"), } # TODO: add uom_id support again def get_report_data(self, ids, context={}): print("stock_card.get_report_data") company_id = get_active_company() comp = get_model("company").browse(company_id) if ids: params = self.read(ids, load_m2o=False)[0] else: params = self.default_get(load_m2o=False, context=context) settings = get_model("settings").browse(1) company_address = settings.default_address_id.address_text location_id = params.get("location_id") product_id = params.get("product_id") if not location_id and not product_id: raise Exception("Please select a product or location") categ_id = params.get("categ_id") uom_id = params.get("uom_id") date_from = params.get("date_from") if not date_from: return date_to = params.get("date_to") if not date_to: return lot_id = params.get("lot_id") invoice_id = params.get("invoice_id") show_pending = params.get("show_pending") hide_zero = params.get("hide_zero") date_from_prev = (datetime.strptime(date_from, "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d") t0 = time.time() print("before get open_totals") open_totals = get_totals(None, date_from_prev, product_id=product_id, location_id=location_id, show_pending=show_pending, lot_id=lot_id, categ_id=categ_id) t1 = time.time() print("get open_totals in %.3f s" % (t1 - t0)) print("len open_totals=%s" % len(open_totals)) prod_open_totals = {} for (prod_id, loc_from_id, loc_to_id), (qty, amt, qty2) in open_totals.items(): prod_open_totals.setdefault(prod_id, {}) prod_open_totals[prod_id][(loc_from_id, loc_to_id)] = (qty, amt, qty2) def _get_open_balance(prod_id, loc_id): bal_qty = 0 bal_amt = 0 bal_qty2 = 0 tots = prod_open_totals.get(prod_id, {}) for (loc_from_id, loc_to_id), (qty, amt, qty2) in tots.items(): if loc_to_id == loc_id and loc_from_id != loc_id: bal_qty += qty bal_amt += amt bal_qty2 += qty2 elif loc_from_id == loc_id and loc_to_id != loc_id: bal_qty -= qty bal_amt -= amt bal_qty2 -= qty2 return bal_qty, bal_amt, bal_qty2 prod_locs = {} db = get_connection() q = "SELECT m.id,m.date,m.ref,m.related_id,m.lot_id,l.number AS lot_num,m.invoice_id,i.number AS invoice_num,m.product_id,m.location_from_id,m.location_to_id,m.qty,m.uom_id,m.cost_amount,m.qty2 FROM stock_move m LEFT JOIN stock_lot l ON l.id=m.lot_id LEFT JOIN account_invoice i ON i.id=m.invoice_id LEFT JOIN product p on m.product_id=p.id WHERE m.date>=%s AND m.date<=%s" args = [date_from + " 00:00:00", date_to + " 23:59:59"] if product_id: q += " AND m.product_id=%s" args.append(product_id) if categ_id: q += " AND p.categ_id=%s" args.append(categ_id) if location_id: q += " AND (m.location_from_id=%s OR m.location_to_id=%s)" args += [location_id, location_id] if lot_id: q += " AND m.lot_id=%s" args.append(lot_id) if invoice_id: q += " AND m.invoice_id=%s" args.append(invoice_id) if show_pending: q += " AND m.state IN ('pending','done')" else: q += " AND m.state='done'" q += " ORDER BY m.date" print("q", q) print("args", args) res = db.query(q, *args) print("%d results" % len(res)) t2 = time.time() print("get stock movements in %.3f s" % (t2 - t1)) for r in res: prod_locs.setdefault((r.product_id, r.location_from_id), []).append(r) prod_locs.setdefault((r.product_id, r.location_to_id), []).append(r) loc_ids = [] prod_ids = [] for prod_id, loc_id in prod_locs: loc_ids.append(loc_id) prod_ids.append(prod_id) loc_ids = list(set(loc_ids)) prod_ids = list(set(prod_ids)) perm_loc_ids = get_model("stock.location").search( [["id", "in", loc_ids]]) prods = {} for prod in get_model("product").browse(prod_ids): prods[prod.id] = { "name": prod.name, "code": prod.code, "uom_id": prod.uom_id.id, } locs = {} for loc in get_model("stock.location").browse(loc_ids): locs[loc.id] = { "name": loc.name, "code": loc.code, "type": loc.type, } t3 = time.time() print("get prod/loc info in %.3f s" % (t3 - t2)) groups = [] i = 0 for (prod_id, loc_id), moves in prod_locs.items(): i += 1 if i % 100 == 0: print("%d/%d" % (i, len(prod_locs))) if loc_id not in perm_loc_ids: continue prod = prods[prod_id] loc = locs[loc_id] if location_id: if loc_id != location_id: continue else: if loc["type"] != "internal": continue bal_qty, bal_amt, bal_qty2 = _get_open_balance(prod_id, loc_id) bal_price = bal_qty and bal_amt / bal_qty or 0 lines = [] line = { "date": date_from, "bal_qty": bal_qty, "bal_cost_amount": bal_amt, "bal_cost_price": bal_price, "bal_qty2": bal_qty2, } lines.append(line) for r in moves: ref = r.ref qty = r.qty * get_model("uom").get_ratio( r.uom_id) / get_model("uom").get_ratio(prod["uom_id"]) amt = r.cost_amount or 0 price = amt / qty if qty else 0 qty2 = r.qty2 or 0 if hide_zero and not qty and not qty2: continue line = { "id": r.id, "date": r.date, "ref": ref, "lot_id": r.lot_id, "lot_num": r.lot_num, "invoice_id": r.invoice_id, "invoice_num": r.invoice_num, } if r.location_to_id == loc_id and r.location_from_id == loc_id: continue elif r.location_to_id == loc_id: bal_qty += qty bal_amt += amt bal_qty2 += qty2 line.update({ "in_qty": qty, "in_cost_price": price, "in_amount": amt, "in_qty2": qty2, }) elif r.location_from_id == loc_id: bal_qty -= qty bal_amt -= amt bal_qty2 -= qty2 line.update({ "out_qty": qty, "out_cost_price": price, "out_amount": amt, "out_qty2": qty2, }) bal_price = bal_qty and bal_amt / bal_qty or 0 line.update({ "bal_qty": bal_qty, "bal_cost_amount": bal_amt, "bal_cost_price": bal_price, "bal_qty2": bal_qty2, }) lines.append(line) group = { "product_id": prod_id, "location_id": loc_id, "product_name": prod["name"], "product_code": prod["code"], "location_name": loc["name"], "location_code": loc["code"], "lines": lines, "total_in_qty": sum(l.get("in_qty", 0) for l in lines), "total_in_amount": sum(l.get("in_amount", 0) for l in lines), "total_in_qty2": sum(l.get("in_qty2", 0) for l in lines), "total_out_qty": sum(l.get("out_qty", 0) for l in lines), "total_out_amount": sum(l.get("out_amount", 0) for l in lines), "total_out_qty2": sum(l.get("out_qty2", 0) for l in lines), } groups.append(group) t4 = time.time() print("make lines in %.3f s" % (t3 - t2)) groups.sort(key=lambda x: (x["product_name"], x["location_name"])) data = { "company_name": comp.name, "tax_no": settings.tax_no, "address": company_address, "date_from": date_from, "date_to": date_to, "groups": groups, "show_qty2": params.get("show_qty2"), } #pprint(data) return data
class ReportStockSummary(Model): _name = "report.stock.summary" _transient = True _fields = { "date_from": fields.Date("From", required=True), "date_to": fields.Date("To", required=True), "location_id": fields.Many2One("stock.location", "Location", on_delete="cascade"), "product_id": fields.Many2One("product", "Product", on_delete="cascade"), "lot_id": fields.Many2One("stock.lot", "Lot / Serial Number"), "container_id": fields.Many2One("stock.container", "Container"), "prod_code": fields.Char("Product Code"), # for enterprise package "prod_categ_id": fields.Many2One("product.categ", "Product Category"), "show_lot": fields.Boolean("Show Lot / Serial Number"), "show_container": fields.Boolean("Show Container"), "show_qty2": fields.Boolean("Show Secondary Qty"), "only_closing": fields.Boolean("Only Show Closing"), "prod_company_id": fields.Many2One("company","Product Company"), } _defaults = { "date_from": lambda *a: date.today().strftime("%Y-%m-01"), "date_to": lambda *a: (date.today() + relativedelta(day=31)).strftime("%Y-%m-%d"), } def get_report_data(self, ids, context={}): print("stock_summary.get_report_data") company_id = get_active_company() comp = get_model("company").browse(company_id) if ids: params = self.read(ids, load_m2o=False)[0] else: params = self.default_get(load_m2o=False, context=context) date_from_m1 = (datetime.strptime(params["date_from"], "%Y-%m-%d") - timedelta(days=1)).strftime("%Y-%m-%d") prod_company_id = params.get("prod_company_id") prod_company_ids = get_model("company").search([["id","child_of",prod_company_id]]) if prod_company_ids: prod_company_ids = tuple(prod_company_ids) else: prod_company_ids = None t0 = time.time() totals = { "open": get_totals(None, date_from_m1, product_id=params.get("product_id"), lot_id=params.get("lot_id"), location_id=params.get("location_id"), container_id=params.get("container_id"), prod_categ_id=params.get("prod_categ_id"), prod_code=params.get("prod_code"), prod_company_ids=prod_company_ids), "period": get_totals(params["date_from"], params["date_to"], product_id=params.get("product_id"), lot_id=params.get("lot_id"), location_id=params.get("location_id"), container_id=params.get("container_id"), prod_categ_id=params.get("prod_categ_id"), prod_code=params.get("prod_code"), prod_company_ids=prod_company_ids), "close": get_totals(None, params["date_to"], product_id=params.get("product_id"), lot_id=params.get("lot_id"), location_id=params.get("location_id"), container_id=params.get("container_id"), prod_categ_id=params.get("prod_categ_id"), prod_code=params.get("prod_code"), prod_company_ids=prod_company_ids), } t1 = time.time() print("get totals in %.3f s" % (t1-t0)) prod_locs = set([]) for prod_id, lot_id, loc_from_id, cont_from_id, loc_to_id, cont_to_id in list(totals["open"].keys()) + list(totals["period"].keys()) + list(totals["close"].keys()): if not params.get("show_lot"): lot_id = -1 if not params.get("show_container"): cont_from_id = -1 cont_to_id = -1 prod_locs.add((prod_id, lot_id, loc_from_id, cont_from_id)) prod_locs.add((prod_id, lot_id, loc_to_id, cont_to_id)) prod_totals={} for when in ("open","period","close"): tots={} for (prod_id, lot_id, loc_from_id, cont_from_id, loc_to_id, cont_to_id), tot in totals[when].items(): tots.setdefault(prod_id,{}) tots[prod_id][(lot_id,loc_from_id,cont_from_id,loc_to_id,cont_to_id)]=tot prod_totals[when]=tots def get_sum(when, prod=None, lot=-1, loc_to=-1, cont_from=-1, loc_from=-1, cont_to=-1): qty = 0 amt = 0 qty2 = 0 tots=prod_totals[when].get(prod,{}) for (lot_id, loc_from_id, cont_from_id, loc_to_id, cont_to_id), tot in tots.items(): if lot != -1 and lot_id != lot: continue if loc_from != -1 and loc_from_id != loc_from: continue if loc_to != -1 and loc_to_id != loc_to: continue if cont_from != -1 and cont_from_id != cont_from: continue if cont_to != -1 and cont_to_id != cont_to: continue qty += tot[0] amt += tot[1] qty2 += tot[2] return (qty, amt, qty2) prod_ids = [] lot_ids = [] loc_ids = [] cont_ids = [] for prod_id, lot_id, loc_id, cont_id in prod_locs: prod_ids.append(prod_id) loc_ids.append(loc_id) if lot_id and lot_id!=-1: lot_ids.append(lot_id) if cont_id and lot_id!=-1: cont_ids.append(cont_id) prod_ids = list(set(prod_ids)) loc_ids = list(set(loc_ids)) perm_loc_ids=get_model("stock.location").search([["id","in",loc_ids]]) lot_ids = list(set(lot_ids)) cont_ids = list(set(cont_ids)) prods = {} for prod in get_model("product").browse(prod_ids): prods[prod.id]={ "name": prod.name, "code": prod.code, "uom_id": prod.uom_id.id, "uom_name": prod.uom_id.name, } locs = {} for loc in get_model("stock.location").browse(loc_ids): locs[loc.id]={ "name": loc.name, "code": loc.code, "type": loc.type, } lots = {} for lot in get_model("stock.lot").browse(lot_ids): lots[lot.id]={ "number": lot.number, } conts = {} for cont in get_model("stock.container").browse(cont_ids): conts[cont.id]={ "number": cont.number, } t2 = time.time() print("get prod/loc/lot/cont info in %.3f s" % (t2-t1)) lines = [] print("num prod_locs", len(prod_locs)) for prod_id, lot_id, loc_id, cont_id in prod_locs: if loc_id not in perm_loc_ids: continue if params.get("product_id") and prod_id != params["product_id"]: continue if params.get("lot_id") and lot_id != params["lot_id"]: continue if params.get("location_id") and loc_id != params["location_id"]: continue if params.get("container_id") and cont_id != params["container_id"]: continue loc = locs[loc_id] if loc["type"] != "internal": continue if cont_id and cont_id != -1: cont = conts[cont_id] else: cont = None prod = prods[prod_id] lot = lots[lot_id] if lot_id and lot_id != -1 else None tot_open_in = get_sum("open", prod=prod_id, lot=lot_id, loc_to=loc_id, cont_to=cont_id) tot_open_out = get_sum("open", prod=prod_id, lot=lot_id, loc_from=loc_id, cont_from=cont_id) tot_period_in = get_sum("period", prod=prod_id, lot=lot_id, loc_to=loc_id, cont_to=cont_id) tot_period_out = get_sum("period", prod=prod_id, lot=lot_id, loc_from=loc_id, cont_from=cont_id) tot_close_in = get_sum("close", prod=prod_id, lot=lot_id, loc_to=loc_id, cont_to=cont_id) tot_close_out = get_sum("close", prod=prod_id, lot=lot_id, loc_from=loc_id, cont_from=cont_id) line_vals = { "prod_id": prod_id, "prod_name": prod["name"], "prod_code": prod["code"], "lot_id": lot_id if lot_id != -1 else None, "lot_num": lot["number"] if lot else None, "uom_name": prod["uom_name"], "loc_id": loc_id, "loc_name": loc["name"], "cont_id": cont_id, "cont_name": cont["number"] if cont else "", "open_qty": tot_open_in[0] - tot_open_out[0], "open_amt": tot_open_in[1] - tot_open_out[1], "open_qty2": tot_open_in[2] - tot_open_out[2], "period_in_qty": tot_period_in[0], "period_in_amt": tot_period_in[1], "period_in_qty2": tot_period_in[2], "period_out_qty": tot_period_out[0], "period_out_amt": tot_period_out[1], "period_out_qty2": tot_period_out[2], "close_qty": tot_close_in[0] - tot_close_out[0], "close_amt": tot_close_in[1] - tot_close_out[1], "close_qty2": tot_close_in[2] - tot_close_out[2], } if params.get("only_closing") and line_vals["close_qty"] == 0: continue if not line_vals["open_qty"] and not line_vals["open_qty2"] \ and not line_vals["period_in_qty"] and not line_vals["period_in_qty2"] \ and not line_vals["period_out_qty"] and not line_vals["period_out_qty2"] \ and not line_vals["close_qty"] and not line_vals["close_qty2"]: continue lines.append(line_vals) t3 = time.time() print("make lines in %.3f s" % (t3-t2)) lines.sort( key=lambda l: (l["prod_code"] or "", l["prod_name"], l["lot_num"] or "", l["loc_name"], l["cont_name"] or "")) data = { "company_name": comp.name, "date_from": params.get("date_from"), "date_to": params.get("date_to"), "show_lot": params.get("show_lot"), "show_container": params.get("show_container"), "show_qty2": params.get("show_qty2"), "only_closing": params.get("only_closing"), "lines": lines, "total_open_amt": sum([l["open_amt"] for l in lines]), "total_open_qty2": sum([l["open_qty2"] for l in lines]), "total_period_in_amt": sum([l["period_in_amt"] for l in lines]), "total_period_in_qty2": sum([l["period_in_qty2"] for l in lines]), "total_period_out_amt": sum([l["period_out_amt"] for l in lines]), "total_period_out_qty2": sum([l["period_out_qty2"] for l in lines]), "total_close_qty": sum([l["close_qty"] for l in lines]), "total_close_amt": sum([l["close_amt"] for l in lines]), "total_close_qty2": sum([l["close_qty2"] for l in lines]), } if params.get("location_id"): loc = get_model("stock.location").browse(params["location_id"]) data["location_id"] = [loc.id, loc.name] if params.get("lot_id"): lot = get_model("stock.lot").browse(params["lot_id"]) data["lot_id"] = params["lot_id"] data["lot_num"] = lot.number if params.get("product_id"): prod = get_model("product").browse(params["product_id"]) data["product_id"] = [prod.id, prod.name_get()[0][1]] if params.get("container_id"): cont = get_model("stock.container").browse(params["container_id"]) data["container_id"] = [cont.id, cont.name_get()[0][1]] return data
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 ReportCustInvoice(Model): _name = "report.cust.invoice" _transient = True _fields = { "date_from": fields.Date("From"), "date_to": fields.Date("To"), "contact_id": fields.Many2One("contact", "Contact", on_delete="cascade"), "show_details": fields.Boolean("Show Details"), } def default_get(self, field_names=None, context={}, **kw): defaults = context.get("defaults", {}) contact_id = defaults.get("contact_id") if contact_id: contact_id = int(contact_id) date_from = defaults.get("date_from") date_to = defaults.get("date_to") return { "contact_id": contact_id, "date_from": date_from, "date_to": date_to, } def get_report_data(self, ids, context={}): company_id = get_active_company() company_ids = get_model("company").search( [["id", "child_of", company_id]]) comp = get_model("company").browse(company_id) if ids: params = self.read(ids, load_m2o=False)[0] else: params = self.default_get(load_m2o=False, context=context) settings = get_model("settings").browse(1) company_name = comp.name contact_id = params.get("contact_id") if contact_id: contact_id = int(contact_id) date_from = params.get("date_from") date_to = params.get("date_to") show_details = params.get("show_details") db = get_connection() q = "SELECT l.id,m.date AS move_date,l.contact_id,p.name AS contact_name,m.number AS move_number,l.description,COALESCE(l.due_date,m.date) AS due_date,l.debit-l.credit AS total_amount,r.number AS reconcile_number,l.reconcile_id FROM account_move_line l JOIN account_account a ON a.id=l.account_id JOIN account_move m ON m.id=l.move_id LEFT JOIN contact p ON p.id=l.contact_id LEFT JOIN account_reconcile r ON r.id=l.reconcile_id WHERE l.move_state='posted' AND a.type='receivable' AND a.company_id IN %s" args = [tuple(company_ids)] if date_from: q += " AND COALESCE(l.due_date,l.move_date)>=%s" args.append(date_from) if date_to: q += " AND COALESCE(l.due_date,l.move_date)<=%s" args.append(date_to) if contact_id: q += " AND l.contact_id=%s" args.append(contact_id) else: q += " AND l.contact_id IS NULL" # XXX if not show_details: q += " AND (l.reconcile_id IS NULL OR r.balance!=0)" q += " ORDER BY COALESCE(l.due_date,m.date),l.id" res = db.query(q, *args) lines = [] for r in res: vals = dict(r) if vals["reconcile_number"] and not vals[ "reconcile_number"].endswith("*"): vals["due_amount"] = 0 else: vals["due_amount"] = vals["total_amount"] lines.append(vals) data = { "company_name": company_name, "date_from": date_from, "date_to": date_to, "lines": lines, "totals": { "amount_total": sum(l["due_amount"] for l in lines), } } return data
class Settings(Model): _name = "settings" _key = ["name"] _audit_log = True _fields = { "name": fields.Char("Display Name"), # not used any more... "legal_name": fields.Char("Legal Name"), # not used any more... "company_type_id": fields.Many2One("company.type", "Organization Type"), "currency_id": fields.Many2One("currency", "Default Currency", multi_company=True), "account_receivable_id": fields.Many2One("account.account", "Account Receivable", multi_company=True), "tax_receivable_id": fields.Many2One("account.tax.rate", "Account Receivable Tax"), "account_payable_id": fields.Many2One("account.account", "Account Payable", multi_company=True), "tax_payable_id": fields.Many2One("account.tax.rate", "Account Payable Tax"), "year_end_day": fields.Selection(_days, "Financial Year End (Day)"), "year_end_month": fields.Selection(_months, "Financial Year End (Month)"), "lock_date": fields.Date("Lock Date"), "nf_email": fields.Char("Email to Netforce"), # XXX: deprecated "share_settings": fields.One2Many("share.access", "settings_id", "Sharing Settings"), "currency_gain_id": fields.Many2One("account.account", "Currency Gain Account", multi_company=True), "currency_loss_id": fields.Many2One("account.account", "Currency Loss Account", multi_company=True), "unpaid_claim_id": fields.Many2One("account.account", "Unpaid Expense Claims Account", multi_company=True), "retained_earnings_account_id": fields.Many2One("account.account", "Retained Earnings Account", multi_company=True), "logo": fields.File("Company Logo", multi_company=True), "package": fields.Char("Package", readonly=True), "version": fields.Char("Version"), "tax_no": fields.Char("Tax ID Number", multi_company=True), "branch_no": fields.Char("Branch Number", multi_company=True), "addresses": fields.One2Many("address", "settings_id", "Addresses", function="get_addresses"), "date_format": fields.Char("Date Format"), "use_buddhist_date": fields.Boolean("Use Buddhist Date"), "phone": fields.Char("Phone", multi_company=True), "fax": fields.Char("Fax", multi_company=True), "website": fields.Char("Website", multi_company=True), "root_url": fields.Char("Root URL"), "sale_journal_id": fields.Many2One("account.journal", "Sales Journal"), "purchase_journal_id": fields.Many2One("account.journal", "Purchase Journal"), "pay_in_journal_id": fields.Many2One("account.journal", "Receipts Journal"), "pay_out_journal_id": fields.Many2One("account.journal", "Disbursements Journal"), "general_journal_id": fields.Many2One("account.journal", "General Journal"), "default_address_id": fields.Many2One("address", "Default Address", function="get_default_address"), "ar_revenue_id": fields.Many2One("account.account", "Revenue Account", multi_company=True), # XXX: rename for report "input_report_id": fields.Many2One("account.account", "Input Vat Account", multi_company=True), # XXX: rename for report "output_report_id": fields.Many2One("account.account", "Output Vat Account", multi_company=True), # XXX: rename for report "wht3_report_id": fields.Many2One("account.account", "WHT3 Account", multi_company=True), # XXX: rename for report "wht53_report_id": fields.Many2One("account.account", "WHT53 Account", multi_company=True), "sale_copy_picking": fields.Boolean("Auto-copy sales orders to goods issue"), "sale_copy_invoice": fields.Boolean("Auto-copy sales orders to customer invoice"), "sale_copy_production": fields.Boolean("Auto-copy sales orders to production"), "rounding_account_id": fields.Many2One("account.account", "Rounding Account", multi_company=True), "production_waiting_suborder": fields.Boolean("Wait Sub-Order"), # XXX: check this "anon_profile_id": fields.Many2One("profile", "Anonymous User Profile"), "pick_in_journal_id": fields.Many2One("stock.journal", "Goods Receipt Journal"), "pick_out_journal_id": fields.Many2One("stock.journal", "Goods Issue Journal"), "pick_internal_journal_id": fields.Many2One("stock.journal", "Goods Transfer Journal"), "stock_count_journal_id": fields.Many2One("stock.journal", "Stock Count Journal"), "landed_cost_journal_id": fields.Many2One("stock.journal", "Landed Cost Journal"), "transform_journal_id": fields.Many2One("stock.journal", "Transform Journal"), "production_journal_id": fields.Many2One("stock.journal", "Production Journal"), "product_borrow_journal_id": fields.Many2One("stock.journal", "Borrow Request Journal"), "stock_cost_mode": fields.Selection( [["periodic", "Periodic"], ["perpetual", "Perpetual"]], "Inventory Costing Mode"), "landed_cost_variance_account_id": fields.Many2One("account.account", "Landed Cost Variance Account", multi_company=True), "est_ship_account_id": fields.Many2One("account.account", "Estimate Shipping Account", multi_company=True), "est_duty_account_id": fields.Many2One("account.account", "Estimate Duty Account", multi_company=True), "act_ship_account_id": fields.Many2One("account.account", "Actual Shipping Account", multi_company=True), "act_duty_account_id": fields.Many2One("account.account", "Actual Duty Account", multi_company=True), "menu_icon": fields.File("Menu Icon"), "stock_cost_auto_compute": fields.Boolean("Auto Compute Cost"), "purchase_copy_picking": fields.Boolean("Auto-copy purchase orders to goods receipt"), "purchase_copy_invoice": fields.Boolean("Auto-copy purchase orders to supplier invoice"), "lot_expiry_journal_id": fields.Many2One("stock.journal", "Lot Expiry Journal"), "auto_create_delivery_order": fields.Boolean("Auto-create delivery orders"), } _defaults = { "package": "free", } def get_address_str(self, ids, context={}): obj = self.browse(ids[0]) if not obj.addresses: return "" addr = obj.addresses[0] return addr.name_get()[0][1] def write(self, ids, vals, **kw): res = super().write(ids, vals, **kw) if "date_format" in vals or "use_buddhist_date" in vals: static.clear_translations() # XXX: rename this def get_fiscal_year_end(self, date=None): if date: d0 = datetime.datetime.strptime(date, "%Y-%m-%d").date() else: d0 = datetime.date.today() settings = self.browse(1) if not settings.year_end_month or not settings.year_end_day: raise Exception("Missing fiscal year end") month = int(settings.year_end_month) day = int(settings.year_end_day) d = datetime.date(d0.year, month, day) if d < d0: d += relativedelta(years=1) return d.strftime("%Y-%m-%d") def get_fiscal_year_start(self, date=None): d1 = self.get_fiscal_year_end(date) d = datetime.datetime.strptime(d1, "%Y-%m-%d") - relativedelta( years=1) + datetime.timedelta(days=1) return d.strftime("%Y-%m-%d") def get_default_address(self, ids, context={}): vals = {} for obj in self.browse(ids): vals[obj.id] = obj.addresses and obj.addresses[0].id or None return vals def get_addresses(self, ids, context={}): vals = {} comp_id = access.get_active_company() for obj in self.browse(ids): res = get_model("address").search( [["settings_id", "=", obj.id], [ "or", ["company_id", "=", None], ["company_id", "child_of", comp_id] ]]) vals[obj.id] = res return vals
class KaoProduct(Model): _inherit = "product" _fields = { "foodtype": fields.Boolean("Foodtype"), "price": fields.Decimal("Price"), }
class ShipMethod(Model): _name = "ship.method" _string = "Shipping Method" _key = ["code"] _fields = { "name": fields.Char("Name", required=True, search=True), "code": fields.Char("Code", search=True), "rates": fields.One2Many("ship.rate", "method_id", "Shipping Rates"), "comments": fields.One2Many("message", "related_id", "Comments"), "sequence": fields.Integer("Sequence", required=True), "type": fields.Selection([], "Type"), "ship_product_id": fields.Many2One("product", "Shipping Product"), "exclude_ship_methods": fields.Many2Many("ship.method", "Exclude Shipping Methods", reltable="m2m_ship_method_exclude", relfield="method1_id", relfield_other="method2_id"), "active": fields.Boolean("Active"), "ship_amount": fields.Decimal("Shipping Amount", function="get_ship_amount"), } _order = "sequence" _defaults = { "active": True, } def create_delivery_order(self, ids, context={}): pass def get_ship_amount(self, ids, context={}): vals = {} contact_id = context.get("contact_id") contact = get_model("contact").browse( contact_id) if contact_id else None addr_id = context.get("ship_address_id") if addr_id: addr = get_model("address").browse(addr_id) else: addr = None order_amount = context.get("order_amount") order_weight = context.get("order_weight") for obj in self.browse(ids): if contact and contact.ship_free: vals[obj.id] = 0 continue amt = None for rate in obj.rates: #print("try rate",rate.id) if rate.country_id and ( not addr or addr.country_id.id != rate.country_id.id): #print(" skip country") continue if rate.province_id and (not addr or addr.province_id.id != rate.province_id.id): #print(" skip province (%s / %s %s)"%(rate.province_id.id,addr.id,addr.province_id.id if addr else None)) continue if rate.district_id and (not addr or addr.district_id.id != rate.district_id.id): #print(" skip district") continue if rate.postal_code and (not addr or addr.postal_code != rate.postal_code): #print(" skip postal code") continue if rate.address_name and (not addr or addr.name != rate.address_name): #print(" skip address name") continue if rate.min_amount and (order_amount is None or order_amount < rate.min_amount): #print(" skip min amount") continue if rate.min_weight and (order_weight is None or order_weight < rate.min_weight): #print(" skip min weight") continue #print(" OK ship_price=%s"%rate.ship_price) if amt is None or rate.ship_price < amt: amt = rate.ship_price #if amt is not None: #print("=> shipping price found: %s"%amt) #else: #print("=> no shipping price found") vals[obj.id] = amt return vals
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 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 ReconcileAdjust(Model): _name = "reconcile.adjust" _transient = True _fields = { "line_id": fields.Many2One("account.statement.line", "Statement Line", required=True, on_delete="cascade"), "amount": fields.Decimal("Adjustment Amount", required=True, readonly=True), "account_id": fields.Many2One("account.account", "Adjustment Account", required=True, on_delete="cascade"), "date": fields.Date("Adjustment Date", required=True), "warning": fields.Boolean("Warning", readonly=True), } def default_get(self, field_names=None, context={}, **kw): if not field_names: return {} line_id = context.get("line_id") if not line_id: return {} line_id = int(line_id) st_line_ids, acc_line_ids = get_model( "account.statement.line").get_reconcile_lines([line_id]) total_st = 0 for st_line in get_model("account.statement.line").browse(st_line_ids): total_st += st_line.received - st_line.spent total_acc = 0 for acc_line in get_model("account.move.line").browse(acc_line_ids): total_acc += acc_line.debit - acc_line.credit amt = total_st - total_acc vals = { "line_id": line_id, "amount": amt, "warning": abs(amt) > 1, "date": time.strftime("%Y-%m-%d"), } return vals def do_adjust(self, ids, context={}): obj = self.browse(ids)[0] st_line = obj.line_id account_id = st_line.statement_id.account_id.id amt = obj.amount vals = { "type": amt > 0 and "in" or "out", "pay_type": "adjust", "date": obj.date, "account_id": account_id, "lines": [("create", { "type": "adjust", "description": "Adjustment", "amount": abs(amt), "account_id": obj.account_id.id, })], } pmt_id = get_model("account.payment").create(vals, context={ "type": vals["type"], "date": obj.date }) get_model("account.payment").post([pmt_id]) pmt = get_model("account.payment").browse(pmt_id) acc_line = pmt.move_id.lines[0] acc_line.write({"statement_lines": [("set", [st_line.id])]}) st_line_ids, acc_line_ids = get_model( "account.statement.line").get_reconcile_lines([st_line.id]) total_st = 0 for st_line in get_model("account.statement.line").browse(st_line_ids): total_st += st_line.received - st_line.spent total_acc = 0 for acc_line in get_model("account.move.line").browse(acc_line_ids): total_acc += acc_line.debit - acc_line.credit if total_st != total_acc: raise Exception("Reconciliation error") get_model("account.statement.line").write(st_line_ids, {"state": "reconciled"}) get_model("account.move.line").write(acc_line_ids, {"state": "reconciled"}) return { "next": { "name": "bank", "mode": "page", "active_id": account_id, "related_tab": 0, } }